【Java 刷题记录】双指针

双指针

1. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

示例 2:

输入: nums = [0]
输出: [0]

提示:

  • 1 <= nums.length <= 104
  • -231 <= nums[i] <= 231 - 1

**进阶:**你能尽量减少完成的操作次数吗?

class Solution {
    public void moveZeroes(int[] nums) { 
        int cur = 0;
        int dest = -1;
        int n = nums.length;
        while(cur < n) {
            if(nums[cur] != 0) {
                dest++;
                int tmp = nums[dest];
                nums[dest] = nums[cur];
                nums[cur] = tmp;
            }
            cur++;
        }
    }
}

2. 复写零

给你一个长度固定的整数数组 arr ,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。

注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西。

示例 1:

输入:arr = [1,0,2,3,0,4,5,0]
输出:[1,0,0,2,3,0,0,4]
解释:调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4]

示例 2:

输入:arr = [1,2,3]
输出:[1,2,3]
解释:调用函数后,输入的数组将被修改为:[1,2,3]

提示:

  • 1 <= arr.length <= 104
  • 0 <= arr[i] <= 9
class Solution {
    public void duplicateZeros(int[] arr) {
        // 1. 找到最后一个复写的数
        int cur = 0;
        int n = arr.length;
        int dest = -1;
        while(dest < n - 1) {
            dest += arr[cur] == 0 ? 2 : 1;
            if(dest < n - 1) {
                cur++;
            }
        }
        // 从后往前复写
        // 规避特殊情况
        if(dest == n) {
            arr[dest - 1] = 0;
            cur --;
            dest -= 2;
        }
        while(cur >= 0) {
            if(arr[cur] != 0) {
                arr[dest--] = arr[cur--];
            }else {
                arr[dest] = 0;
                arr[dest - 1] = 0;
                cur--;
                dest -= 2;
            }
        }
    }
}

3. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

示例 2:

输入:n = 2
输出:false

提示:

  • 1 <= n <= 231 - 1
class Solution {

    public int function(int n) {
        int sum = 0;
        while(n != 0) {
            int num = n % 10;
            sum += num * num;
            n /= 10;
        }
        return sum;
    }

    public boolean isHappy(int n) {
        int num1 = n;
        int num2 = n;
        while(true) {
            num1 = function(num1);
            num2 = function(function(num2));
            if(num1 == num2) {
                return num1 == 1;
            }
        }
    }
}

4. 盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0)(i, height[i])

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

**说明:**你不能倾斜容器。

示例 1:

img

输入:[1,8,6,2,5,4,8,3,7]
输出:49 
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例 2:

输入:height = [1,1]
输出:1

提示:

  • n == height.length
  • 2 <= n <= 105
  • 0 <= height[i] <= 104
class Solution {
    public int maxArea(int[] height) {
        int n = height.length;
        int left = 0;
        int right = n - 1;
        int ret = 0;
        while(left < right) {
            int h = 0;
            if(height[left] < height[right]) {
                h = height[left];
                left++;
            }else {
                h = height[right];
                right--;
            }
            ret = Math.max(ret, (right - left + 1) * h);
        }
        return ret;
    }
}

5.有效三角形的个数

给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。

示例 1:

输入: nums = [2,2,3,4]
输出: 3
解释:有效的组合是: 
2,3,4 (使用第一个 2)
2,3,4 (使用第二个 2)
2,2,3

示例 2:

输入: nums = [4,2,3,4]
输出: 4

提示:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] <= 1000
class Solution {

    public int getValidNumber(int[] nums, int left, int right, int flag) {
        int sum = 0;
        while(left < right) {
            if(nums[left] + nums[right] > flag) {
                sum += right - left;
                right--;
            }else {
                left++;
            }
        }
        return sum;
    }

    public int triangleNumber(int[] nums) {
        Arrays.sort(nums);
        int sum = 0;
        for(int i = nums.length - 1; i >= 2; i--) {
            sum += getValidNumber(nums, 0, i - 1, nums[i]);
        }
        return sum;
    }
}

6. 查找总价格为目标值的两个商品

购物车内的商品价格按照升序记录于数组 price。请在购物车中找到两个商品的价格总和刚好是 target。若存在多种情况,返回任一结果即可。

示例 1:

输入:price = [3, 9, 12, 15], target = 18
输出:[3,15] 或者 [15,3]

示例 2:

输入:price = [8, 21, 27, 34, 52, 66], target = 61
输出:[27,34] 或者 [34,27]

提示:

  • 1 <= price.length <= 10^5
  • 1 <= price[i] <= 10^6
  • 1 <= target <= 2*10^6
class Solution {
    public int[] twoSum(int[] price, int target) {
        int n = price.length;
        if(n < 2) {
            return null;
        }
        int left = 0;
        int right = n - 1;
        while(left < right) {
            int sum = price[left] + price[right];
            if(sum > target) {
                right--;
            }else if(sum < target) {
                left++;
            }else {
                break;
            }
        }
        return left == right ? null : new int[]{price[left], price[right]};
    }
}

7. 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

**注意:**答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105
class Solution {

    public void culAndAdd(int[] nums, List<List<Integer>> list, int left, int right, int flag) {
        while(left < right) {
            int sum = nums[left] + nums[right] + flag;
            if(sum > 0) {
                right--;
                while(right > left && nums[right] == nums[right + 1]) right--;
            }else if(sum < 0) {
                left++;
                while(left < right && nums[left] == nums[left - 1]) left++;
            }else {
                list.add(Arrays.asList(nums[left++], nums[right--], flag));
                while(left < right && nums[left] == nums[left - 1]) left++;
                while(right > left && nums[right] == nums[right + 1]) right--;
            }
        }
    }

    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        List<List<Integer>> list = new ArrayList<>();
        for(int i = n - 1; i >= 2;) {
            if(nums[i] < 0) {
                break;
            }
            culAndAdd(nums, list, 0, i - 1, nums[i]);
            i--;
            while(i >= 2 && nums[i] == nums[i + 1]) i--;
        }
        return list;
    }
}

8. 四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

示例 1:

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

示例 2:

输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]

提示:

  • 1 <= nums.length <= 200
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
class Solution {

    public void calAndAdd(int[] nums, List<List<Integer>> list, int left, int right, int flag1, int flag2, int target) {
        while(left < right) {
            long sum = 0L + nums[left] + nums[right] + flag1 + flag2;
            if(sum > target) {
                right--;
                while(left < right && nums[right] == nums[right + 1]) right--;
            }else if(sum < target) {
                left++;
                while(left < right && nums[left] == nums[left - 1]) left++;
            }else {
                list.add(Arrays.asList(nums[left++], nums[right--], flag1, flag2));
                while(left < right && nums[right] == nums[right + 1]) right--;
                while(left < right && nums[left] == nums[left - 1]) left++;
            }
        }
    }

    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        int n = nums.length;
        List<List<Integer>> list = new ArrayList<>();
        if(n < 4) {
            return list;
        }
        for(int i = n - 1; i >= 3;) {
            if(nums[i] < 0 && nums[i] <= target) {
                break;
            }
            for(int j = i - 1; j >= 2;) {
                if(nums[j] < 0 && nums[j] <= target - nums[i]) {
                    break;
                }
                calAndAdd(nums, list, 0, j - 1, nums[j], nums[i], target);
                j--;
                while(j >= 2 && nums[j] == nums[j + 1]) j--;
            }
            i--;
            while(i >= 3 && nums[i] == nums[i + 1]) i--;
        }
        return list;
    }
}

滑动窗口

9. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target

找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组

[numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度**。**如果不存在符合条件的子数组,返回 0

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

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

提示:

  • 1 <= target <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 105
class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int sum = 0;
        int len = Integer.MAX_VALUE;
        for(int left = 0, right = 0; right < nums.length; right++) {
            sum += nums[right];
            while(sum >= target) {
                len = Math.min(len, right - left + 1);
                sum -= nums[left++];
            }
            if(right < left) break;
        }
        return len == Integer.MAX_VALUE ? 0 : len;
    }
}

10. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
  请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

提示:

  • 0 <= s.length <= 5 * 104
  • s 由英文字母、数字、符号和空格组成
class Solution {
    public int lengthOfLongestSubstring(String ss) {
        boolean[] hash = new boolean[128];
        char[] s = ss.toCharArray();
        int len = 0;
        for(int left = 0, right = 0; right < s.length; right++) {
            char ch = s[right];
            while(hash[(int)ch]) {
                hash[(int)(s[left])] = false;
                left++;
            }
            hash[(int)ch] = true;
            len = Math.max(len, right - left + 1);
        }
        return len;
    }
} 

11. 最大连续1的个数 III

给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k0 ,则返回 数组中连续 1 的最大个数

示例 1:

输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。

示例 2:

输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。

提示:

  • 1 <= nums.length <= 105
  • nums[i] 不是 0 就是 1
  • 0 <= k <= nums.length
class Solution {
    public int longestOnes(int[] nums, int k) {
        int zeroCount = 0;
        int n = nums.length;
        int left = 0;
        int right = left;
        int ret = 0;
        while(left <= right && right < n) {
            // 进窗口
            if(nums[right++] == 0) zeroCount++;
            // 判断
            while(zeroCount > k) {
                // 出窗口
                if(nums[left++] == 0) zeroCount--;
            }
            // 更新结果
            ret = Math.max(right - left, ret);
        }
        return ret;
    }
}

12. 将 x 减到 0 的最小操作数

给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。

如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1

示例 1:

输入:nums = [1,1,4,2,3], x = 5
输出:2
解释:最佳解决方案是移除后两个元素,将 x 减到 0 。

示例 2:

输入:nums = [5,6,7,8,9], x = 4
输出:-1

示例 3:

输入:nums = [3,2,20,1,1,3], x = 10
输出:5
解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 104
  • 1 <= x <= 109
class Solution {
    public int minOperations(int[] nums, int x) {
        int n = nums.length;
        int sum = 0;
        for(int i = 0; i < n; i++) {
            sum += nums[i];
        }
        int target = sum - x;
        if(target == 0) {
            return n;
        }
        if(target < 0) {
            return -1;
        }
        int value = 0;
        int left = 0;
        int right = left;
        int len = -1;
        while(left <= right && right < n) {
            // 1. 进窗口
            value += nums[right++];
            // 2. 判断
            while(left < right && value > target) {
                value -= nums[left++];
            }
            if(value == target) {
                len = Math.max(len, right - left);
            }
        }
        return len == -1 ? -1 : n - len;
    }
}

13. 水果成篮

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

  • 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
  • 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

示例 1:

输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。

示例 2:

输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。

示例 3:

输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。

示例 4:

输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。

提示:

  • 1 <= fruits.length <= 105
  • 0 <= fruits[i] < fruits.length
class Solution {
    public int totalFruit(int[] fruits) {
        int n = fruits.length;
        int[] hash = new int[n];
        int left = 0;
        int right = left;
        int ret = 0;
        int count = 0;
        while(left <= right && right < n) {
            // 1. 进窗口
            int keyR = fruits[right++];
            if(hash[keyR] == 0)  count++;
            hash[keyR]++;
            // 2.判断
            while(count > 2) {
                // 3. 出窗口(不能直接删除)
                int ketL = fruits[left++];
                hash[ketL]--;
                if(hash[ketL] == 0) count--;
            }
            // 4. 更新结果
            ret = Math.max(ret, right - left);
        }
        return ret;

    }
}

14. 找到字符串中所有字母异位词

给定两个字符串 sp,找到 s 中所有 p异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例 1:

输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

示例 2:

输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

提示:

  • 1 <= s.length, p.length <= 3 * 104
  • sp 仅包含小写字母
class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> ret = new ArrayList<>();
        int[] hash1 = new int[26];
        int[] hash2 = new int[26];
        int lengthS = s.length();
        int lengthP = p.length();
        char[] ss = s.toCharArray();
        char[] pp = p.toCharArray();
        for(int i = 0; i < lengthP; i++) {
            hash2[pp[i] - 'a']++;
        }
        int left = 0;
        int right = left;
        int count = 0;
        int effect = 0;
        while(left <= right && right < lengthS) {
            // 1. 进窗口
            while(count < lengthP && right < lengthS) {
                int indexR = ss[right++] - 'a';
                hash1[indexR]++;
                if(hash1[indexR] <= hash2[indexR]) effect++;
                count++;
            }
            // 判断
            if(effect == lengthP) {
                ret.add(left);
            }
            //2. 出窗口
            int indexL = ss[left++] - 'a';
            if(hash1[indexL] <= hash2[indexL]) effect--;
            hash1[indexL]--;
            count--;
        }
        return ret;
    }
}

15. 串联所有单词的子串

给定一个字符串 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 <= s.length <= 104
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 30
  • words[i]s 由小写英文字母组成
class Solution {

    public List<Integer> findSubstring(String s, String[] words) {
        // 1. 准备工作
        int sLength = s.length(); // 字符串长度
        int n = words.length; // 单词个数
        int wordLength = words[0].length();// 单词大小
        List<Integer> retList = new ArrayList<>();// 结果集
        Map<String, Integer> hash2 = new HashMap<>(); // words 的哈希表
        for(String word : words) {
            hash2.put(word, hash2.getOrDefault(word, 0) + 1);
        }
        // 2. 进行 wordLength 次滑动窗口
        for(int i = 0; i < wordLength; i++) {
            Map<String, Integer> hash1 = new HashMap<>(); // s 的哈希表
            int left = i;
            int right = left;
            int count = 0;
            int effect = 0;
            while(left <= right && right + wordLength <= sLength) {
                // 1. 进窗口
                while(count < n && right + wordLength <= sLength) {
                    String str = s.substring(right, right + wordLength);
                    hash1.put(str, hash1.getOrDefault(str, 0) + 1);
                    if(hash1.get(str) <= hash2.getOrDefault(str, 0)) effect++;
                    count++;
                    right += wordLength;
                }
                // 判断
                if(count != n) break;
                if(effect == n) {
                    retList.add(left);
                }
                // 2. 出窗口
                if(left + wordLength <= sLength) {
                    String str = s.substring(left, left + wordLength);
                    if(hash1.get(str) <= hash2.getOrDefault(str, 0)) effect--;
                    hash1.put(str, hash1.get(str) - 1);
                    count--;
                    left += wordLength;
                }
            }
        }
        return retList;
    }
}

16. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

示例 2:

输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

提示:

  • m == s.length
  • n == t.length
  • 1 <= m, n <= 105
  • st 由英文字母组成
class Solution {
    public String minWindow(String s, String t) {
        int length = s.length();
        char[] arr1 = s.toCharArray();
        char[] arr2 = t.toCharArray();
        int[] hash1 = new int[128];
        int[] hash2 = new int[128];
        int count = 0;//目标种类数
        for(char ch : arr2) {
            int index = (int)ch;
            if(hash2[index] == 0) count++;
            hash2[index]++;
        }
        int effect = 0;//有效种类数
        int left = 0;
        int right = left;
        int retStart = -1;
        int retEnd = -1;
        int retLen = Integer.MAX_VALUE;
        while(left <= right && right < length) {
            // 1. 进窗口
            int indexR = (int)arr1[right++];
            if(++hash1[indexR] == hash2[indexR]) effect++;
            // 判断 + 循环
            while(effect == count && left <= right) {
                int len = right - left;
                if(len < retLen) {
                    retStart = left;
                    retEnd = right;
                    retLen = len;
                }
                // 2. 出窗口
                int indexL = (int)arr1[left++];
                if(hash1[indexL]-- == hash2[indexL]) effect--;
            }
        }
        return retStart == -1 ? "" : s.substring(retStart, retEnd);
    }
}

二分查找

17. 二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:

  1. 你可以假设 nums 中的所有元素是不重复的。
  2. n 将在 [1, 10000]之间。
  3. nums 的每个元素都将在 [-9999, 9999]之间。
class Solution {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target) left = mid + 1;
            else if(nums[mid] > target) right = mid - 1;
            else return mid;
        }
        return -1;
    }
}

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

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

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

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 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]

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] ret = {-1, -1};
        // 处理边界情况
        int n = nums.length;
        if(n == 0) {
            return ret;
        }
        // 找左端点
        int left = 0;
        int right = n - 1;
        // 这代表 left 不可能加一后比 right 大,所以结束循环的时候一定是 left == right
        while(left < right) {
            int mid = left + (right - left) / 2;  
            if(nums[mid] < target) left = mid + 1;
            else right  = mid;
        }
        if(nums[right] == target) ret[0] = right;
        else return ret;
        // 找右端点
        // 能到这里说明这个就是左端点,通过这个 left + left右边的元素找右端点即可
        // 即使不提前返回,那么left 要么还是 0,要么就是 n - 1;
        // left = 0; 
        right = n - 1;
        // 这代表 right 不可能减一后比 left 小,所以结束循环的时候一定是 left == right
        while(left < right) {
            int mid = left + (right - left + 1) / 2;
            if(nums[mid] > target) right = mid - 1;
            else left = mid;
        }
        if(nums[left] == target) ret[1] = left;
        else return ret;
        return ret;
    }
}

19. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:

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

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums无重复元素升序 排列数组
  • -104 <= target <= 104
class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] >= target) right = mid;
            else left = mid + 1;
        }
        // 这里 left 和 right 必然是相遇的
        if(nums[right] < target) return right + 1;
        return right;
    }
}

20. x 的平方根

给你一个非负整数 x ,计算并返回 x算术平方根

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

**注意:**不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5

示例 1:

输入:x = 4
输出:2

示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

提示:

  • 0 <= x <= 231 - 1
class Solution {
    public int mySqrt(int x) {
        if(x == 0) return 0;
        int left = 1;
        int right = x;
        while(left < right) {
            int mid = left + (right - left + 1) / 2;
            if(1L * mid * mid > x) right = mid - 1;
            else left = mid;
        }
        return left;
    }
}

21. 山脉数组的峰顶索引

符合下列属性的数组 arr 称为 山脉数组

  • arr.length >= 3

  • 存在

    i
    

    0 < i < arr.length - 1
    

    )使得:

    • arr[0] < arr[1] < ... arr[i-1] < arr[i]
    • arr[i] > arr[i+1] > ... > arr[arr.length - 1]

给你由整数组成的山脉数组 arr ,返回满足 arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1] 的下标 i

你必须设计并实现时间复杂度为 O(log(n)) 的解决方案。

示例 1:

输入:arr = [0,1,0]
输出:1

示例 2:

输入:arr = [0,2,1,0]
输出:1

示例 3:

输入:arr = [0,10,5,2]
输出:1

提示:

  • 3 <= arr.length <= 105
  • 0 <= arr[i] <= 106
  • 题目数据保证 arr 是一个山脉数组
class Solution {
    public int peakIndexInMountainArray(int[] arr) {
        int left = 1;
        int right = arr.length - 2;
        while(left < right) {
            int mid = left + (right - left + 1) / 2;
            if(arr[mid] > arr[mid - 1]) left = mid;
            else right = mid - 1;
        }
        return left;
    }
}

22. 寻找峰值

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞

你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

示例 1:

输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。

示例 2:

输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5 
解释:你的函数可以返回索引 1,其峰值元素为 2;
     或者返回索引 5, 其峰值元素为 6。

提示:

  • 1 <= nums.length <= 1000
  • -231 <= nums[i] <= 231 - 1
  • 对于所有有效的 i 都有 nums[i] != nums[i + 1]
class Solution {
    public int findPeakElement(int[] nums) {
        int left = 0;
        int n = nums.length;
        int right = n - 1;
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(mid + 1 == n || nums[mid] > nums[mid + 1]) right = mid;
            else left = mid + 1;
        }
        return left;
    }
}

23. 寻找旋转排序数组中的最小值

已知一个长度为 n 的数组,预先按照升序排列,经由 1n旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 3 次得到输入数组。

示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

提示:

  • n == nums.length
  • 1 <= n <= 5000
  • -5000 <= nums[i] <= 5000
  • nums 中的所有整数 互不相同
  • nums 原来是一个升序排序的数组,并进行了 1n 次旋转
class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        int y = nums[right];
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] > y) left = mid + 1;
            else right = mid;
        }
        return nums[left];
    }
}

24. 点名

某班级 n 位同学的学号为 0 ~ n-1。点名结果记录于升序数组 records。假定仅有一位同学缺席,请返回他的学号。

示例 1:

输入: records = [0,1,2,3,5]
输出: 4

示例 2:

输入: records = [0, 1, 2, 3, 4, 5, 6, 8]
输出: 7

提示:

1 <= records.length <= 10000
class Solution {
    public int takeAttendance(int[] records) {
        int left = 0;
        int right = records.length - 1;
        if(records[right] == right) return right + 1;
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(records[mid] == mid) left = mid + 1;
            else right = mid;
        }
        return right;
    }
}
  • 12
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

s:103

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值