算法基础五

移除元素

给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

解题思路:这里数组的删除并不是真的删除,只是将要删除的元素移动到数组的后面,然后返回数组实际有效的长度即可。

java 复制代码
    public int removeElement(int[] nums, int val) {
        if (nums == null || nums.length == 0) {
            return 0;
        }

        int len = nums.length;
        int index = 0;
        for(int i = 0; i < len; i++) {
            if (nums[i] != val) {
                nums[index++] = nums[i];                
            }
        }
        return index;
    }

两数相除

给你两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用 乘法、除法和取余运算。

整数除法应该向零截断,也就是截去(truncate)其小数部分。例如,8.345 将被截断为 8 ,-2.7335 将被截断至 -2 。

返回被除数 dividend 除以除数 divisor 得到的 商 。

示例 1:

输入: dividend = 10, divisor = 3 输出: 3 解释: 10/3 = 3.33333... ,向零截断后得到 3 。

示例 2:

输入: dividend = 7, divisor = -3 输出: -2 解释: 7/-3 = -2.33333... ,向零截断后得到 -2 。

解题思路:可以用二分搜索来做。把除法运算后的商作为搜索目标,商的取值范围是[0,dividend]。利用二分好到(商 + 1)* 除数>被除数并且 商 * 除数 < 被除数或者 (商 + 1)* 除数 > 被除数 并且 商 * 除数 < 被除数的时候,就算找到商了,其余情况继续二分即可。

java 复制代码
public int divide(int dividend, int divisor) {
   // 考虑被除数为最小值的情况
     if (dividend == Integer.MIN_VALUE) {
         if (divisor == 1) {
             return Integer.MIN_VALUE;
         }
         if (divisor == -1) {
             return Integer.MAX_VALUE;
         }
     }
     
     // 考虑除数为最小值的情况
     if (divisor == Integer.MIN_VALUE) {
         return dividend == Integer.MIN_VALUE ? 1 : 0;
     }
     // 考虑被除数为 0 的情况
     if (dividend == 0) {
         return 0;
     }
     
     // 一般情况,使用二分查找
     // 将所有的正数取相反数,这样就只需要考虑一种情况
     boolean rev = false;
     if (dividend > 0) {
         dividend = -dividend;
         rev = !rev;
     }
     if (divisor > 0) {
         divisor = -divisor;
         rev = !rev;
     }
     
     int left = 1, right = Integer.MAX_VALUE, ans = 0;
     while (left <= right) {
         // 注意溢出,并且不能使用除法
         int mid = left + ((right - left) >> 1);
         boolean check = quickAdd(divisor, mid, dividend);
         if (check) {
             ans = mid;
             // 注意溢出
             if (mid == Integer.MAX_VALUE) {
                 break;
             }
             left = mid + 1;
         } else {
             right = mid - 1;
         }
     }

     return rev ? -ans : ans;
 }

 // 快速乘
 public boolean quickAdd(int y, int z, int x) {
     // x 和 y 是负数,z 是正数
     // 需要判断 z * y >= x 是否成立
     int result = 0, add = y;
     while (z != 0) {
         if ((z & 1) != 0) {
             // 需要保证 result + add >= x
             if (result < x - add) {
                 return false;
             }
             result += add;
         }
         if (z != 1) {
             // 需要保证 add + add >= x
             if (add < x - add) {
                 return false;
             }
             add += add;
         }
         // 不能使用除法
         z >>= 1;
     }
     return true;
 }

串联所有单词的子串

给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。

例如,如果 words = ["ab","cd","ef"], 那么 "abcdef", "abefcd","cdabef", "cdefab","efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。

返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。

示例 1:

输入:s = "barfoothefoobarman", words = ["foo","bar"] 输出:[0,9]

解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。 子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。 子串 "foobar" 开始位置是 9。它是words 中以 ["foo","bar"] 顺序排列的连接。 输出顺序无关紧要。返回 [9,0] 也是可以的。

示例 2:

输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"] 输出:[]

解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。 s 中没有子串长度为 16 并且等于 words的任何顺序排列的连接。 所以我们返回一个空数组。

示例 3:

输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"],输出:[6,9,12]

解释:因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。 子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。 子串 "barthefoo" 开始位置是 9。它是 words 中以["bar","the","foo"] 顺序排列的连接。 子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。

解题思路:这道题有两个前提条件。1,字符串数组里面的字符串长度都是一样的;2,字符串数组中的字符串都要连续连在一起,前后顺序可以任意组合。

先将字符串数组中所有字符串都存到map中,并累计出现的次数,然后从源字符串从头开始遍历,每次判断字符串数组里面的字符串是否全部用完,如果全部用完并且长度正好是字符串数组任意排列组合的长度,就记录这个组合的起始下标。

java 复制代码
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> res = new ArrayList<Integer>();
        int wordNum = words.length;
        if (wordNum == 0) {
            return res;
        }

        int wordLen = words[0].length();
        //HashMap1 存所有单词,记录出现的次数
        HashMap<String, Integer> allWords = new HashMap<String, Integer>();
        for (String w : words) {
            int value = allWords.getOrDefault(w, 0);
            allWords.put(w, value + 1);
        }
        //遍历所有子串
        for (int i = 0; i < s.length() - wordNum * wordLen + 1; i++) {
            //HashMap2 存当前扫描的字符串含有的单词
            HashMap<String, Integer> hasWords = new HashMap<String, Integer>();
            int num = 0;
            //判断该子串是否符合
            while (num < wordNum) {
                String word = s.substring(i + num * wordLen, i + (num + 1) * wordLen);
                //判断该单词在 HashMap1 中
                if (allWords.containsKey(word)) {
                    int value = hasWords.getOrDefault(word, 0);
                    hasWords.put(word, value + 1);
                    //判断当前单词的 value 和 HashMap1 中该单词的 value
                    if (hasWords.get(word) > allWords.get(word)) {
                        break;
                    }
                } else {
                    break;
                }
                num++;
            }
            //判断是不是所有的单词都符合条件
            if (num == wordNum) {
                res.add(i);
            }
        }
        return res;
    }

搜索旋转排列数组

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0 输出:4

示例 2:

输入:nums = [4,5,6,7,0,1,2], target = 3 输出:-1

示例 3:

输入:nums = [1], target = 0 输出:-1

解题思路:给定一个升序排列的数组,并且无重复值。需要把后面随机一段有序数组放到数组前面,形成前后两段有序的子数组。因为数组有序,可以使用二分搜索算法来实现。

java 复制代码
  public int search(int[] nums, int target) {
        int len = nums.length;
        if (len == 0) {
            return - 1;
        }

        if (len == 1) {
            return nums[0] == target ? 0 : -1;
        }

        int l = 0;
        int r = len - 1;
        while(l <= r) {
            int mid = (l + r) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            if(nums[0] <= nums[mid]) {
                if (nums[0] <= target && target < nums[mid]) {
                    r = mid - 1;
                } else {
                    l = mid + 1;
                }
            } else {
                if (nums[mid] < target && target <= nums[len - 1]) {
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            }
        }
        return -1;
    }

在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8 输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6 输出:[-1,-1]

示例 3:

输入:nums = [], target = 0 输出:[-1,-1]

解题思路:二分搜搜算法

java 复制代码
 public int[] searchRange(int[] nums, int target) {
        int leftIdx = binarySearch(nums, target, true);
        int rightIdx = binarySearch(nums, target, false) - 1;

        if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] == target && nums[rightIdx] == target) {
            return new int[]{leftIdx, rightIdx};
        }
        return new int[] {-1, -1};

    }

    public int binarySearch(int[] nums, int target, boolean lower) {
        int left = 0;
        int right = nums.length - 1;
        int ans = nums.length;
        while(left <= right ) {
            int mid = (left + right) / 2;
            if (nums[mid] > target || (lower && nums[mid] >= target)) {
                right = mid - 1;
                ans = mid;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }
相关推荐
qystca1 分钟前
洛谷 P11242 碧树 C语言
数据结构·算法
冠位观测者8 分钟前
【Leetcode 热题 100】124. 二叉树中的最大路径和
数据结构·算法·leetcode
悲伤小伞13 分钟前
C++_数据结构_详解二叉搜索树
c语言·数据结构·c++·笔记·算法
醒了就刷牙14 分钟前
黑马Java面试教程_P9_MySQL
java·mysql·面试
m0_7482336420 分钟前
SQL数组常用函数记录(Map篇)
java·数据库·sql
m0_675988231 小时前
Leetcode3218. 切蛋糕的最小总开销 I
c++·算法·leetcode·职场和发展
编程爱好者熊浪1 小时前
JAVA HTTP压缩数据
java
吴冰_hogan1 小时前
JVM(Java虚拟机)的组成部分详解
java·开发语言·jvm
开心工作室_kaic1 小时前
springboot485基于springboot的宠物健康顾问系统(论文+源码)_kaic
spring boot·后端·宠物
0zxm1 小时前
08 Django - Django媒体文件&静态文件&文件上传
数据库·后端·python·django·sqlite