在初始化的时候定义一个数组用来存储每个节点对应的元素之和,在需要求解特定索引区间时只需要用简单的加减法就可以得到结果。可以大大降低时间复杂度。
【适⽤于频繁对原始数组的某个区间的元素进⾏增减,原始数组的元素发生变化】
Difference():初始化差分数组diff,diff中存储的是相邻元素之间的差值(后一个减去前一个);
increment():根据题目要求对diff进行调整;
如:对区间[i, j](要注意题目中改变的元素是否包括右边界的元素)的元素增加1,则只需要
diff[i] += 1;
根据j的索引位置不同,有不同的情况,如果j + 1 >= diff.length,表示对i之后的所有元素都增加1,只需要执行上面那一条语句就可以;如果j + 1 < diff.length,表示还需要对j后面的元素进行处理,否则j后面的元素也会增加1。
if (j + 1 < diff.length) {
diff[j + 1] -= 1;
}
result():根据差分数组diff复原数组nums。
// 差分数组⼯具类
class Difference {
// 差分数组
private int[] diff;
/* 输⼊⼀个初始数组,区间操作将在这个数组上进⾏ */
public Difference(int[] nums) {
if (nums.length == 0) return ;
diff = new int[nums.length];
// 根据初始数组构造差分数组
diff[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
diff[i] = nums[i] - nums[i - 1];
}
}
/* 给闭区间 [i,j] 增加 val(可以是负数)*/
public void increment(int i, int j, int val) {
diff[i] += val;
if (j + 1 < diff.length) {
diff[j + 1] -= val;
}
}
/* 返回结果数组 */
public int[] result() {
int[] res = new int[diff.length];
// 根据差分数组构造结果数组
res[0] = diff[0];
for (int i = 1; i < diff.length; i++) {
res[i] = res[i - 1] + diff[i];
}
return res;
}
}
大佬给出的框架:
/* 滑动窗⼝算法框架 */
void slidingWindow(string s, string t) {
unordered_map need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
// c 是将移⼊窗⼝的字符
char c = s[right];
// 右移窗⼝
right++;
// 进⾏窗⼝内数据的⼀系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗⼝是否要收缩
while (window needs shrink) {
// d 是将移出窗⼝的字符
char d = s[left];
// 左移窗⼝
left++;
// 进⾏窗⼝内数据的⼀系列更新
...
}
}
}
自己改了一个Java版本的:
/* 滑动窗⼝算法框架 */
void slidingWindow(string s, string t) {
Map need = new HashMap<>();
Map window = new HashMap<>();
char[] ct = t.toCharArray();
//先把t中的字符及个数存入need中
for (char c : ct) need.put(c, need.getOrDefault(c, 0) + 1);
int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
// c 是将移⼊窗⼝的字符
char c = s.charAt(right);
// 右移窗⼝
right++;
// 进⾏窗⼝内数据的⼀系列更新
...
/*** debug 输出的位置 ***/
System.out.println("window: [" + left + "," + right + "]");
/********************/
// 判断左侧窗⼝是否要收缩
while (window needs shrink) {
// d 是将移出窗⼝的字符
char d = s.charAt(left);
// 左移窗⼝
left++;
// 进⾏窗⼝内数据的⼀系列更新
...
}
}
}
如果不用Map存储,选择用数据存储,总结了一个大致的框架
/* 滑动窗⼝算法框架 */
void slidingWindow(string s, string t) {
// 长度定位128是因为使用ASCII码存储字符
int[] need = new int[128];
char[] ct = t.toCharArray();
//先把t中的字符及个数存入need中
for (char c : ct) need[c]++;
int left = 0, right = 0;
int cnt = t.length();
while (right < s.length()) {
// c 是将移⼊窗⼝的字符
char c = s.charAt(right);
// 如果c是需要的字符
if (need[c] > 0) {
cnt--;
}
need[c]--;
// 进⾏窗⼝内数据的⼀系列更新
...
if (cnt == 0) {
// 此时说明字符达到了要求
while (left < right && need[s.charAt(left)] < 0) {
// 此时是在收缩窗口,先去除不是t中的字符
need[s.charAt(left)]++;
left++;
}
if (// 通常这里是判断当前窗口是否符合题意) {
...
}
// 不是t中的都处理完后,当前left指向的就是t中需要的,所以cnt也要变化
need[s.charAt(left)]++;
left++;
cnt++;
}
right++;
}
}
大佬的框架
int binarySearch(int[] nums, int target) {
int left = 0, right = ...;
while(...) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
left = ...
} else if (nums[mid] > target) {
right = ...
}
}
return ...;
}
在while()中,如果写的是left <= right,则更新right时,需要令right = mid - 1,因为查找区间时左闭右闭[left, right];如果写的是left < right,则right = mid,因为此时是左闭右开的区间[left, right)
1.当需要查找特定元素时
while(left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
}...
2.当需要查找左侧边界时(假设数据是升序排列的,所以调整right,如果是降序的,应该调整left)
while(left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
right = mid;
}...
在末尾检验一下
if (left >= nums.length || nums[left] != target) return -1;
return left;
3.当需要查找右侧边界时(假设数据是升序排列的,所以调整right,如果是降序的,应该调整left)
while(left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
left = mid + 1;
}...
在末尾检验一下(循环退出时left == right,所以写left或者right都一样,return left - 1是因为调整left时是mid + 1,即mid = left - 1,)
if (left < 0 || nums[left - 1] != target) return -1;
return left - 1;
可以理解为把典型二分搜索问题中的数组nums[ ]变成了f(x),这个f(x)需要自己找出逻辑关系
定义两个指针,分别为慢指针slowIndex,快指针fastIndex。
在删除数组/链表中元素,判断链表是否有环,合并链表等问题中非常适用,具体可以看算法小抄
ListNode reverse(ListNode head) {
if (head.next == null) return head;
ListNode last = reverse(head.next);
head.next.next = head;
head.next = null;
return last;
}
单纯看代码有点绕,举了一个简单的例子,比较容易理解。
(大佬在原文中建议是从函数的定义去理解,相当于图里的r(1)的单层逻辑)
算法小抄https://labuladong.gitee.io/algo/https://labuladong.gitee.io/algo/