2、数组问题最常见
1、二分查找法
template<typename T>
int binarySearch1(T arr[], int n, T target) {
// 在 [l ... r] 的范围里寻找 target
int l = 0;
int r = n - 1;
int mid;
// 当 l == r 时, 区间 [l ... r] 依然是有效的
while (l <= r) {
mid = l + (r - l) / 2;
if (arr[mid] == target) return mid;
if (arr[mid] < target) l = mid + 1;
else r = mid - 1;
}
return -1;
}
template<typename T>
int binarySearch2(T arr[], int n, T target) {
// 在 [l ... r) 的范围里寻找 target
int l = 0;
int r = n;
int mid;
// 当 l < r 时, 区间 [l ... r) 就是有效的
while (l < r) {
mid = l + (r - l) / 2;
if (arr[mid] == target) return mid;
if (arr[mid] < target) l = mid + 1;
else r = mid;
}
return -1;
}
2、移动零
/**
* 时间复杂度: O(N)
* 空间复杂度: O(N)
*/
public static void moveZeroes1(int[] nums) {
int[] temp = new int[nums.length];
int index = 0;
for (int num : nums) {
if (num != 0) temp[index++] = num;
}
System.arraycopy(temp, 0, nums, 0, temp.length);
}
/**
* 时间复杂度: O(N)
* 空间复杂度: O(1)
*/
public static void moveZeroes2(int[] nums) {
// [0 ... p) 存放的都是非 0 元素
int p = 0;
for (int num : nums) {
if (num != 0) nums[p++] = num;
}
Arrays.fill(nums, p, nums.length, 0);
}
/**
* 时间复杂度: O(N)
* 空间复杂度: O(1)
*/
public static void moveZeroes3(int[] nums) {
// [0 ... p) 存放的都是非 0 元素
int p = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
if (p != i) swap(nums, p++, i);
else p++;
}
}
}
private static void swap(int[] nums, int a, int b) {
int k = nums[a];
nums[a] = nums[b];
nums[b] = k;
}
3、颜色分类
/**
* 计数排序思路
* 时间复杂度: O(N)
* 空间复杂度: O(R), R 为元素的取值范围, 在这里 R = 3
*/
public static void sortColors1(int[] nums) {
// nums 中的元素固定为 0, 1, 2
// 处理元素取值范围是 [0, R) 的计数排序
int R = 3;
int[] cnt = new int[R];
for (int num : nums) cnt[num]++;
// [index[i], index[i + 1]) 区间的值为 i
int[] index = new int[R + 1];
for (int i = 0; i < R; i++) {
// 右边界 = 左边界 + cnt[i]
index[i + 1] = index[i] + cnt[i];
}
for (int i = 0; i + 1 < index.length; i++) {
// [index[i], index[i + 1]) 区间的值为 i
for (int j = index[i]; j < index[i + 1]; j++) nums[j] = i;
}
}
/**
* 三路快速排思路
* 时间复杂度: O(N)
* 空间复杂度: O(1)
*/
public static void sortColors2(int[] nums) {
int p1 = -1;
int p2 = nums.length;
int i = 0;
while (i < p2) {
if (nums[i] == 0) swap(nums, ++p1, i++);
else if (nums[i] == 2) swap(nums, --p2, i);
else i++;
}
}
private static void swap(int[] nums, int a, int b) {
int k = nums[a];
nums[a] = nums[b];
nums[b] = k;
}
4、两数之和 II - 输入有序数组
4.1、二分搜索思路
public static int[] twoSum(int[] numbers, int target) {
for (int i = 0; i < numbers.length; i++) {
int complement = target - numbers[i];
int res = binarySearch(numbers, i + 1, numbers.length - 1, complement);
if (res != -1) return new int[]{i + 1, res + 1};
}
return new int[]{-1, -1};
}
private static int binarySearch(int[] arr, int l, int r, int target) {
int mid;
while (l <= r) {
mid = l + (r - l) / 2;
if (arr[mid] == target) return mid;
else if (arr[mid] > target) r = mid - 1;
else l = mid + 1;
}
return -1;
}
4.2、对撞指针思路
public static int[] twoSum(int[] numbers, int target) {
int p1 = 0;
int p2 = numbers.length - 1;
while (p1 < p2) {
int sum = numbers[p1] + numbers[p2];
if (sum == target) return new int[]{p1 + 1, p2 + 1};
else if (sum < target) p1++;
else p2--;
}
return new int[]{-1, -1};
}
5、滑动窗口
滑动窗口算法通常使用双指针来维护窗口的左右边界,右指针用于扩展窗口,左指针用于缩小窗口
在每一次移动窗口时,我们都可以根据窗口的状态来更新答案
5.1、长度最小的子数组
209 - 长度最小的子数组
minSubArrayLen1 和 minSubArrayLen2 均可
public static int minSubArrayLen1(int target, int[] nums) {
// nums[l ... r] 是滑动窗口
int l = 0;
int r = -1;
int sum = 0;
int res = nums.length + 1;
// 窗口的左边界在数组范围内, 则循环继续
while (l < nums.length) {
if (r + 1 < nums.length && sum < target) sum += nums[++r];
else sum -= nums[l++];
if (sum >= target) res = Math.min(res, r - l + 1);
}
return res != nums.length + 1 ? res : 0;
}
public static int minSubArrayLen2(int target, int[] nums) {
// nums[l ... r] 是滑动窗口
int l = 0;
int r = -1;
int sum = 0;
int res = nums.length + 1;
// 窗口的右边界可以继续扩展, 则循环继续
while (r + 1 < nums.length) {
// r 用于扩展窗口
while (r + 1 < nums.length && sum < target) sum += nums[++r];
if (sum >= target) res = Math.min(res, r - l + 1);
// l 用于缩小窗口
while (l <= r && sum >= target) {
sum -= nums[l++];
if (sum >= target) res = Math.min(res, r - l + 1);
}
}
return res != nums.length + 1 ? res : 0;
}
5.2、无重复字符的最长子串
3 - 无重复字符的最长子串
推荐 lengthOfLongestSubstring3
public static int lengthOfLongestSubstring1(String s) {
// s[l ... r] 是滑动窗口
int l = 0;
int r = -1;
int[] freq = new int[256]; // freq[i] 代表字符 i 出现的频率
int res = 0;
// 窗口的左边界在字符串范围内, 则循环继续
while (l < s.length()) {
if (r + 1 < s.length() && freq[s.charAt(r + 1)] == 0) freq[s.charAt(++r)]++;
else freq[s.charAt(l++)]--;
res = Math.max(res, r - l + 1);
}
return res;
}
public static int lengthOfLongestSubstring2(String s) {
// s[l ... r] 是滑动窗口
int l = 0;
int r = -1;
int[] index = new int[256]; // index[i] 代表字符 i 在 s 中的索引
Arrays.fill(index, -1);
int res = 0;
// 窗口的左边界在字符串范围内, 则循环继续
while (l < s.length()) {
if (r + 1 < s.length() && index[s.charAt(r + 1)] == -1) index[s.charAt(++r)] = r;
else index[s.charAt(l++)] = -1;
res = Math.max(res, r - l + 1);
}
return res;
}
public static int lengthOfLongestSubstring3(String s) {
// s[l ... r] 是滑动窗口
int l = 0;
int r = -1;
int[] index = new int[256]; // index[i] 代表字符 i 在 s 中的索引
Arrays.fill(index, -1);
int res = 0;
// 窗口的右边界可以继续扩展, 则循环继续
while (r + 1 < s.length()) {
// r 用于扩展窗口
while (r + 1 < s.length() && index[s.charAt(r + 1)] == -1) index[s.charAt(++r)] = r;
res = Math.max(res, r - l + 1);
// l 用于缩小窗口
if (r + 1 < s.length()) {
int i;
for (i = l; i <= index[s.charAt(r + 1)]; i++) index[s.charAt(i)] = -1;
l = i;
}
}
return res;
}
6、更多题目
6.1、找到字符串中所有字母异位词
438 - 找到字符串中所有字母异位词
推荐 findAnagrams1 和 findAnagrams3
public static List<Integer> findAnagrams1(String s, String p) {
List<Integer> list = new ArrayList<>();
if (s.length() < p.length()) return list;
int[] freq_p = new int[26];
for (char c : p.toCharArray()) freq_p[c - 'a']++;
// s[l ... r] 是滑动窗口
int l = 0;
int r = l + p.length() - 1;
int[] freq_s = new int[26];
for (int i = l; i <= r; i++) freq_s[s.charAt(i) - 'a']++;
if (same(freq_s, freq_p)) list.add(l);
// 窗口的右边界可以继续扩展, 则循环继续
while (r + 1 < s.length()) {
freq_s[s.charAt(++r) - 'a']++;
freq_s[s.charAt(l++) - 'a']--;
if (same(freq_s, freq_p)) list.add(l);
}
return list;
}
public static List<Integer> findAnagrams2(String s, String p) {
List<Integer> list = new ArrayList<>();
if (s.length() < p.length()) return list;
int[] freq_p = new int[26];
for (char c : p.toCharArray()) freq_p[c - 'a']++;
// s[l ... r] 是滑动窗口
int l = 0;
int r = -1;
int[] freq_s = new int[26];
// 窗口的右边界可以继续扩展, 则循环继续
while (r + 1 < s.length()) {
if (r - l + 1 != p.length()) {
freq_s[s.charAt(++r) - 'a']++;
if (r - l + 1 == p.length() && same(freq_s, freq_p)) list.add(l);
} else {
freq_s[s.charAt(++r) - 'a']++;
freq_s[s.charAt(l++) - 'a']--;
if (same(freq_s, freq_p)) list.add(l);
}
}
return list;
}
public static List<Integer> findAnagrams3(String s, String p) {
List<Integer> list = new ArrayList<>();
if (s.length() < p.length()) return list;
int[] freq_p = new int[26];
for (char c : p.toCharArray()) freq_p[c - 'a']++;
int[] freq_s = new int[26];
for (int i = 0; i < s.length(); i++) {
freq_s[s.charAt(i) - 'a']++;
// s[i - len + 1 ... i]
if (i >= p.length() - 1) {
if (same(freq_s, freq_p)) list.add(i - p.length() + 1);
freq_s[s.charAt(i - p.length() + 1) - 'a']--;
}
}
return list;
}
private static boolean same(int[] freq_s, int[] freq_p) {
for (int i = 0; i < 26; i++) {
if (freq_s[i] != freq_p[i]) return false;
}
return true;
}
6.2、最小覆盖子串
76 - 最小覆盖子串
minWindow1 和 minWindow2 均可
public static String minWindow1(String s, String t) {
int[] freq_t = new int[256];
for (char c : t.toCharArray()) freq_t[c]++;
int startIndex = -1;
int minLength = s.length() + 1;
// s[l ... r] 是滑动窗口
int l = 0;
int r = -1;
int[] freq_s = new int[256];
int sCnt = 0; // sCnt 代表窗口中包含的 t 中字符的个数
// 窗口的左边界在字符串范围内, 则循环继续
while (l < s.length()) {
if (r + 1 < s.length() && sCnt < t.length()) {
char c = s.charAt(r + 1);
freq_s[c]++;
if (freq_s[c] <= freq_t[c]) sCnt++;
r++;
} else {
if (sCnt == t.length() && r - l + 1 < minLength) {
minLength = r - l + 1;
startIndex = l;
}
char c = s.charAt(l);
freq_s[c]--;
if (freq_s[c] < freq_t[c]) sCnt--;
l++;
}
}
if (startIndex != -1) return s.substring(startIndex, startIndex + minLength);
return "";
}
public static String minWindow2(String s, String t) {
int[] freq_t = new int[256];
for (char c : t.toCharArray()) freq_t[c]++;
int startIndex = -1;
int minLength = s.length() + 1;
// s[l ... r] 是滑动窗口
int l = 0;
int r = -1;
int sCnt = 0; // sCnt 代表窗口中包含的 t 中字符的个数
int[] freq_s = new int[256];
// 窗口的右边界可以继续扩展, 则循环继续
while (r + 1 < s.length()) {
while (r + 1 < s.length() && sCnt < t.length()) {
char c = s.charAt(r + 1);
freq_s[c]++;
if (freq_s[c] <= freq_t[c]) sCnt++;
r++;
}
while (sCnt == t.length()) {
if (r - l + 1 < minLength) {
startIndex = l;
minLength = r - l + 1;
}
char c = s.charAt(l);
freq_s[c]--;
if (freq_s[c] < freq_t[c]) sCnt--;
l++;
}
}
if (startIndex != -1) return s.substring(startIndex, startIndex + minLength);
return "";
}
本文来自博客园,作者:lidongdongdong~,转载请注明原文链接:https://www.cnblogs.com/lidong422339/p/17360227.html