双指针
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:
输入:[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 != j
、i != k
且j != 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
a
、b
、c
和d
互不相同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
,如果可以翻转最多k
个0
,则返回 数组中连续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. 找到字符串中所有字母异位词
给定两个字符串
s
和p
,找到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
s
和p
仅包含小写字母
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
s
和t
由英文字母组成
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
提示:
- 你可以假设
nums
中的所有元素是不重复的。n
将在[1, 10000]
之间。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
的数组,预先按照升序排列,经由1
到n
次 旋转 后,得到输入数组。例如,原数组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
原来是一个升序排序的数组,并进行了1
至n
次旋转
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;
}
}