数组双指针——二分查找:
二分不仅在数组这种有明显边界的情况下使用,在无明显边界的如,无穷小和无穷大的一个极端情况,也可以使用。
这种情况下,nums[mid]就由自己确定的f(mid)来定,且要考虑同为target时,要满足题目条件,还需要取满足target的更大/小的mid。
int start = 0,end = nums.length - 1;
int mid;
while(start <= end){
mid = (start + end) / 2;
if(nums[mid] > target){
end = mid - 1;
}else if(nums[mid] < target){
start = mid + 1;
}else{
return mid;
}
}
875. 爱吃⾹蕉的珂珂
⼆分搜索的套路⽐较固定,如果遇到⼀个算法问题,能够确定 x, f(x), target分别是什么,并写出单调函数f 的代码。 这题珂珂吃⾹蕉的速度就是⾃变量 x,吃完所有⾹蕉所需的时间就是单调函数 f(x),target 就是吃⾹蕉的时间限制 H。
上面的就是自变量是mid,f(x)即为nums[mid],target即为限制,即目标值。
上下算法不同主要是因为下面这个吃香蕉问题,即使在H正好吃完也可能有多个mid速度,那么根据题目意思,需要取最小的,慢慢吃。
int left = 1;
int right = 1000000000 + 1;
int mid;
while(left < right){
mid = (left + right) / 2;
if(getTime(piles,mid) <= h){
right = mid; //如果能在这个时间吃完,那么要保证最终能吃得完。
//满足条件的情况下尽可能取小的值,所以是right = mid
}else{
left = mid + 1;
}
}
return left;
}
// 返回以速度v吃完香蕉需要的时间
public int getTime(int[] piles,int v){
int hours = 0;
for(int i = 0;i < piles.length;i++){
hours += piles[i] / v;
if(piles[i] % v > 0){
hours++;
}
}
return hours;
876. 在 D 天内送达包裹的能⼒
无边界的二分查找,要看好边界条件,同时考虑要满足题目条件,还需要取满足target的更大/小的mid。
//二分查找,自变量mid就是weights,target是days.但要求最低运载能力,即同days时,要选择更小的weights,nums[mid]这里就是f(x)
int left = 1,right = 25000001, mid;
while(left < right){
mid = (left + right) / 2;
if(getdays(weights,mid) <= days){
right = mid;
}else{
left = mid + 1;// > days说明完不成,自然不能保留,<=days说明完的成,保留,right= mid
}
}
return left; //或者right反正最后left = right,因为while中设置的left < right.
}
public int getdays(int[] weights,int w){
int days = 0, sum = 0,i = 0;
while(i < weights.length){
if(weights[i] > w) return 50001;
sum += weights[i];
if(sum > w){
days++;
sum = weights[i];
}
i++;
}
return days + 1;
数组双指针——滑动窗口:
什么是滑动窗口?
其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!
如何移动?
我们只要把队列的左边的元素移出就行了,直到满足题目要求!
一直维持这样的队列,找出队列出现最长的长度时候,求出解!
即遍历增大窗口,特定条件缩小窗口。
int left = 0, right = 0;
while (right < s.size()) {
// 增大窗口
window.add(s[right]);
right++;
while (window needs shrink) {
// 缩小窗口
window.remove(s[left]);
left++;
}
}
3. 无重复字符的最长子串
本题,利用 Map<Character,Integer> hashmap 存储字符和遍历至此时,该字符最靠右的索引
hashmap.containsKey(s.charAt(i))?
left = Math.max(left,hashmap.get(s.charAt(i)) + 1); // 不直接left=hashmap.get(s.charAt(i)) + 1是因为left左边的字符和索引也在hashmap里,那些索引在left左边.此行代码实现窗口移动。
这里的hashmap不是队列,所以会存在包含的元素索引在left左边
这里的left和i共同构成窗口,left实现移动,移动与否取决于hashmap.containsKey(s.charAt(i))?
left = Math.max(left,hashmap.get(s.charAt(i)) + 1)
if(s.length() <= 1) return s.length();
Map<Character,Integer> hashmap = new HashMap<>(); //用于存储字符和遍历至此时,该字符最靠右的索引
int maxlen = 0;
int left = 0;
for(int i = 0;i < s.length();i++){
if(hashmap.containsKey(s.charAt(i))){
left = Math.max(left,hashmap.get(s.charAt(i)) + 1); // 不直接left=hashmap.get(s.charAt(i)) + 1是因为left左边的字符和索引也在hashmap里,那些索引在left左边.此行代码实现窗口移动。
}
hashmap.put(s.charAt(i),i); //存储字符和索引,包含时更新索引。
maxlen = Math.max(maxlen,i - left + 1);
}
return maxlen;
438. 找到字符串中所有字母异位词
自编:思路就是找到p存在的字符,就进入窗口从0开始增大,不满足异位时缩小窗口至0;
可行,但是超过时间限制。
List<Integer> list = new ArrayList();
List<Character> listp = new ArrayList();
int plen = p.length();
for(int i = 0;i < plen;i++){
listp.add(p.charAt(i));
}
//滑动窗口,遍历增大窗口,条件下缩小窗口.
int left = 0;
int right =0;
while(right <= s.length() - plen){
if(listp.contains(s.charAt(right))){
List<Character> templist = new ArrayList(listp);
left = right;//窗口大小从0开始
//从0开始增大窗口
while(left < right + plen){
if(!templist.remove(Character.valueOf(s.charAt(left)))){
break;
}
left++;
}
if(templist.isEmpty()){
list.add(left - plen);
}
}
right++;
}
return list;
官方思路:
固定窗口大小进行滑动,比较滑动窗口中各种字符个数与p中是否一致;
字符个数统计,利用数组[26]。表示26个字母各自个数
s.charAT(i) - ‘a’(a-z字符转数字整形)即可得到个数
// 固定窗口大小进行滑动,比较滑动窗口中各种字符个数与p中是否一致;
// 字符个数统计,利用数组[26]。表示26个字母各自个数
// s.charAT(i) - 'a'(a-z字符转数字整形)即可得到个数
int slen = s.length(),plen = p.length();
if(slen < plen) return new ArrayList();
List<Integer> list = new ArrayList();
//初始窗口初始化:
int[] pcount = new int[26],scount = new int[26];
for(int i = 0;i < plen;i++){
pcount[p.charAt(i) - 'a'] += 1;
scount[s.charAt(i) - 'a'] += 1;
}
if(Arrays.equals(pcount,scount)){
list.add(0);
}
for(int i = 0;i < slen - plen;i++){
scount[s.charAt(i) - 'a'] -= 1;
scount[s.charAt(i + plen) - 'a'] += 1;
if(Arrays.equals(pcount,scount)){
list.add(i + 1);
}
}
return list;
567.字符串排列
思路同上438
链表双指针:
2.俩数相加:双指针分别指向l1,l2
// 模拟:代码优化,主要是在一个while内完成所有进位模拟计算,有一个node为null时,加值设为0即可。
// 相应的要在循环内部对可能出现的l1,l2为null的情况进行处理
int count = 0,num1,num2;//表示进位
ListNode temp1 = new ListNode(0);
ListNode result = temp1;
while(l1 != null || l2 != null){
num1 = (l1 == null ? 0 : l1.val);
num2 = (l2 == null ? 0 : l2.val);
temp1.next = new ListNode((num1 + num2 + count) % 10);
count = (num1 + num2 + count) / 10;
temp1 = temp1.next;
l1 = (l1 == null ? l1 : l1.next);
l2 = (l2 == null ? l2 : l2.next);
}
if(count == 1){
temp1.next = new ListNode(1);
}
return result.next;
- 删除链表的倒数第 N 个结点
//倒数第n个节点,就是正数第len-n+1个。可以正向遍历至该处进行删除,
//也可以双指针相隔n个节点距离,后一个指针为null时,前一个指针删除即可。
int len = 0;
ListNode temp = new ListNode();
temp = head;
while(temp != null){
len++;
temp = temp.next;
}
if(len == 1 || len == n) return head.next;
temp = head;
for(int i = 1;i < len - n;i++){
temp = temp.next;
}
temp.next = temp.next.next;
return head;
//相隔n个节点的双指针.为了方便可在head前加一个0节点,用于left指针起步
ListNode start = new ListNode(0,head);
ListNode left = start;
ListNode right = head;
for(int i = 0;i < n;i++){
right = right.next;
}
while(right != null){
left = left.next;
right = right.next;
}
left.next = left.next.next;
return start.next;
21.合并俩个有序链表
双指针即可/亦可利用递归,递归函数里只对俩个链表的首节点对比,较小者置于前面,将剩余链表送入递归之中
// //双指针+遍历循环
// if(list1 == null) return list2;
// if(list2 == null) return list1;
// ListNode ResultNode = new ListNode(0);
// ListNode p = ResultNode;
// while(list1 != null && list2 != null){
// if(list1.val <= list2.val){
// p.next = list1;
// list1 = list1.next;
// }else{
// p.next = list2;
// list2 = list2.next;
// }
// p = p.next;
// }
// if(list1 == null) p.next = list2;
// if(list2 == null) p.next = list1;
// return ResultNode.next;
//递归
if(l1 == null) return l2;
if(l2 == null) return l1;
if(l1.val < l2.val){
l1.next = mergeTwoLists(l1.next,l2);
return l1;
}else{
l2.next = mergeTwoLists(l1,l2.next);
return l2;
}
141.环形链表
哈希表:有环则会在哈希表内重复节点
快慢双指针,ListNode fastNode = head.next.next, slowNode = head.next;有环则必定相遇
// //1.哈希表法
// //空间复杂度不满足进阶
// if(head == null || head.next == null) return false;
// Set<ListNode> nodeSet = new HashSet();
// ListNode p = head;
// while(p != null) {
// if(!nodeSet.add(p)){
// return true;
// }
// p = p.next;
// }
// return false;
//2.快慢指针,
//若有环则必定相遇
if(head == null || head.next == null) return false;
ListNode fastNode = head.next.next, slowNode = head.next;
while(fastNode != slowNode) {
if(fastNode == null) return false;
else if(fastNode.next == null) return false;
fastNode = fastNode.next.next;
slowNode = slowNode.next;
}
return true;
142.环形链表Ⅱ
同上。相遇后,另一个快指针从head重新出发,且速度均为一,再次相遇即为环的入口。
if(head == null || head.next == null) return null;
ListNode fast = head.next.next, slow = head.next;//既然指针没移动时,会导致head相等,那么在进入循环前先使得来指针先各移动一次
while(fast != slow) {
if(fast == null) {return null;}
else if(fast.next == null){return null;}
//不能写成fastNode.next == null || fastNode.next.next == null。这样可能fast.next已经为null就不存在fastNode.next.next
fast = fast.next.next;
slow = slow.next;
}
fast = head;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return fast;
160.相交链表:
1.哈希表,将节点存入哈希表中,另一个链表的节点遍历在哈希表中查找即可。
2.只要在相同速度为1的双指针各自走到null时,令其从另一个链表的head处开始继续.next即可。
俩个都经过null,再从另一个链表开始遍历至交汇处时,共同走了俩链表交点前的长度和+交点后的长度,则有交点必会相遇。
3.双指针,长的链表先遍历俩者长度差值的部分,以便俩指针从距离交点相同的距离开始共同遍历
// //2.双指针遍历,至Null后跳转另一个链表继续遍历。若俩者长度相同,则第一次相遇即为交点,若长度不同,且不相交,会在遍历彼此链表时到null处相同,即返回null
// ListNode pa = headA, pb = headB;
// while(pa != pb){
// pa = pa == null ? headB:pa.next;
// pb = pb == null ? headA:pb.next;
// }
// return pa;
//3.双指针,长的链表先遍历俩者长度差值的部分,以便俩指针从距离交点相同的距离开始共同遍历
ListNode pa = headA, pb = headB;
//获取长度
int la = 0, lb = 0;
while(pa != null){
pa = pa.next;
la++;
}
while(pb != null){
pb = pb.next;
lb++;
}
//长的链表遍历至距离交点相同距离的地方
pa = headA;
pb = headB;
int flag = la - lb;
if(flag >= 0){
for(int i = 0;i < flag;i++){
pa = pa.next;
}
}else{
for(int i = 0;i < -flag;i++){
pb = pb.next;
}
}
//俩指针共同遍历
while(pa != pb){
pa = pa.next;
pb = pb.next;
}
return pa; //null说明无交点,俩者都遍历结束
876.链表的中间节点
1.获取长度,按19.倒数第n节点思路来做
2.快慢指针,中间节点则选择速度为1、2的快慢双指针即可。
fast != null && fast.next != null
ListNode slow = head, fast = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
return slow;
- 删除排序链表中的重复元素
单指针遍历即可
//1.单指针+遍历
if (head == null) return null;
ListNode p = head;
while(p.next != null){
if(p.val == p.next.val){
p.next = p.next.next;
}else{
p = p.next;
}
}
return head;
- 反转链表
***ListNode pre = null, curr = head, temp = head;***双指针+临时指针进行反转操作
if(head == null || head.next == null) return head;
ListNode pre = null, curr = head, temp = head;
while(curr != null){
temp = temp.next;
curr.next = pre;
pre = curr;
curr = temp;
}
return pre;
92.反转链表Ⅱ
记录left前的一个节点pre,从left开始反转后面节点至right,记录原顺序时right.next的节点after。
至此反转完成,pre.next = right;left.next = after
重点:ListNode start = new ListNode(0,head);,加个零起始节点便于处理包括链表只有一个节点的情况。(因为算法中有前中后三个节点,当链表只有一个节点时,算法的三个节点会有一个不存在)
为避免最后一次反转,midafter不存在,最后一次反转安排在循环外
一次遍历完成如下:
//记录left前的一个节点pre,从left开始反转后面节点至right,记录原顺序时right.next的节点after。
//至此反转完成,pre.next = right;left.next = after
// if(head.next == null) return head;
ListNode start = new ListNode(0,head);
ListNode pre = start;
//先找pre
for(int i = 0;i < left - 1;i++){
pre = pre.next;
}
ListNode leftnode = pre.next;
ListNode midpre = null,mid = leftnode,midafter = leftnode.next;
for(int i = 0;i < right - left;i++){
mid.next = midpre; //反转当前节点
midpre = mid;
mid = midafter;
midafter = mid.next;
}
mid.next = midpre; // 考虑最后一次反转,mid成为Null,midafter会不存在,所以最后一次反转放在循环外
ListNode rightnode = mid,after = midafter;
pre.next = rightnode;
leftnode.next = after;
return start.next;
方法2.也可以先遍历一遍链表,反转left和right生成新链表,
再次遍历链表至left前和right后,完成拼接
25. K 个⼀组翻转链表
根据上题思路,本题只不过是需要先遍历链表确定链表长度,确定要多少次完成k组反转,然后遍历链表,对k内进行上题的left到left+k的反转
在这里插入代码片
- 回⽂链表
法1.将值复制到数组中,进行双指针正序逆序的移动对比
法2.先确定中点(快慢双指针,以head为起点即可不考虑奇偶情况),反转中点以后的链表,然后双指针遍历**前续链表,和反转的后续链表,**直至前续链表指针为null
//链表中节点数目在范围[1, 105] 内
//找中点
ListNode slow = head, fast = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
//从slow开始反转后续链表
slow = reverseNode(slow);
ListNode slowsave = slow;
//反转后续链表slowpre
fast = head;
while(slow != null){
if(fast.val != slow.val){
slowsave = reverseNode(slowsave); // 链表后半段反转回去
return false;
}
fast = fast.next;
slow = slow.next;
}
slowsave = reverseNode(slowsave);
return true;
}
public ListNode reverseNode(ListNode node){
ListNode slowpre = null, slownext = node;
while(node != null){
slownext = slownext.next;
node.next = slowpre;
slowpre = node;
node = slownext;
}
return slowpre;
前缀和:
标准的前缀和问题,核⼼思路是⽤⼀个新的数组 preSum 记录 nums[0…i-1] 的累加和
即.构造前缀数组presums
- 区域和检索 - 数组不可变
this.presums[i] = this.presums[i - 1] + nums[i];
本题注意点:left为0时,该边界条件下无prenums[0-1],且此时结果就为presums[right]
class NumArray {
public int[] presums;
public NumArray(int[] nums) {
this.presums = nums;
for(int i = 1;i < nums.length;i++){
this.presums[i] = this.presums[i - 1] + nums[i];
}
}
public int sumRange(int left, int right) {
//这里要注意,当left为0时,不存在prenums[0-1],
if(left == 0){
return presums[right];
}else{
return presums[right] - presums[left - 1];
}
}
}
304.⼆维区域和检索 - 矩阵不可变
本题重点:为避免分类,即有col1 - 1/row1 - 1 = -1的情况,可以给二维矩阵扩充0行0列,这样就可以全部为第四种情况,即可不分类
学习顿悟:遇到需要分类讨论的如链表,数组,二维数组的索引index - 1时,若index = 0,则index - 1 就会超出索引范围,此时即可扩充零节点,零元素,零行零列
class NumMatrix {
public int[][] matrixsums;
public NumMatrix(int[][] matrix) {
//为避免分类,即有col1 - 1/row1 - 1 = -1的情况,可以给二维矩阵扩充0行0列,这样就可以全部为第四种情况,即可不分类
int row = matrix.length,col = matrix[0].length;
matrixsums = new int[row + 1][col + 1];
for(int i = 0;i < row + 1;i++){
matrixsums[i][0] = 0;
}
for(int i = 0;i < col + 1;i++){
matrixsums[0][i] = 0;
}
for(int i = 1;i < row + 1;i++){
for(int j = 1;j < col + 1;j++){
matrixsums[i][j] = matrixsums[i - 1][j] + matrixsums[i][j - 1] - matrixsums[i - 1][j - 1] + matrix[i - 1][j - 1];
}
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
//分类处理,为避免分类,即有col1 - 1/row1 - 1 = -1的情况,可以给二维矩阵扩充0行0列,这样就可以全部为第四种情况,即可不分类
return matrixsums[row2 + 1][col2 + 1] - matrixsums[row1][col2 + 1] - matrixsums[row2 + 1][col1] + matrixsums[row1][col1];
// if(row1 == 0 && col1 == 0){
// return matrixsums[row2][col2];
// }else if(row1 == 0){
// return matrixsums[row2][col2] - matrixsums[row2][col1 - 1];
// }else if(col1 == 0){
// return matrixsums[row2][col2] - matrixsums[row1 - 1][col2];
// }else{
// return matrixsums[row2][col2] - matrixsums[row1 - 1][col2] - matrixsums[row2][col1 - 1] + matrixsums[row1 - 1][col1 - 1];
// }
}
}
- 和为 K 的子数组
前缀和+哈希表优化(利用前缀和将本题转为:数组中找俩数和为目标值target,即可用哈希表来优化求解
每遍历一个数字,即查看表中有无对应匹配的另一个数字,并获取个数,同时本数字也要存入表中,次数也要叠加
//1.暴力枚举
//2.前缀和+哈希表优化(类比题目数组找俩数和为target
int result = 0;
//扩充0元素以避免边界条件的分类讨论
int[] sums = new int[nums.length + 1];
sums[0] = 0;
for(int i = 0;i < nums.length;i++){
sums[i + 1] = sums[i] + nums[i];
}
Map<Integer,Integer> hashmap = new HashMap();
for(int i = 0;i < sums.length;i++){
if(hashmap.containsKey(sums[i] - k)){
result += hashmap.get(sums[i] - k);
}
if(hashmap.containsKey(sums[i])){
hashmap.put(sums[i],hashmap.get(sums[i]) + 1);
}else{
hashmap.put(sums[i],1);
}
}
return result;
差分数组
差分数组技巧适⽤于频繁对数组区间进⾏增减的场景。
进⾏区间增减,如果你想对区间 nums[i…j] 的元素全部加 3,那么只需要让 diff[i] += 3,然后再 让 diff[j+1] -= 3 即可:
差分数组模板:
其中:diff[i] += k;因为diff是差分数组,所以该行代码对差分换原的原数组从i开始的后面所有元素增加了k;但是我们要在区间i-j之间进行操作,那么就需要*对j以后的元素将增加的操作减去,即diff[j+1] -= 3。*但是这里有个边界条件,即区间右侧是数组最右端时,就不存在j+1的索引,因此可用if语句if(j != n){diff[j+1] -= 3};或者,利用前面提到的去除边界条件的讨论,可以增加零元素,只不过这次是增加在差分数组后面(因为边界问题的产生是在差分数组的最右端)
注意:差分数组的构造与换原,首元素都是不变的。
//1.构造差分数组
int[] diff = new int[n];
diff[0] = nums[0];
for(int i = 1;i < n;i++){
diff[i] = nums[i] - nums[i - 1];
}
//区间增减
diff[i] += k;
diff[j+1] -= k;//这里注意在区间i-j之间进行增减,
//3.换原差分数组
int[] result = new int[n];
result[0] = diff[0];
for(int i = 1;i < n;i++){
result[i] = result[i - 1] + diff[i];
}
1109.航班预订统计
diff差分数组右侧边界问题用if语句解决:
//差分数组技巧适⽤于频繁对数组区间进⾏增减的场景。
int[] nums = new int[n];
for(int i = 0;i < n;i++){
nums[i] = 0;
}
//1.构造差分数组
int[] diff = new int[n];
diff[0] = nums[0];
for(int i = 1;i < n;i++){
diff[i] = nums[i] - nums[i - 1];
}
//2.进⾏区间增减,i-j区间是针对数组的i-1,j-1之间,且j = n时,即为数组最右边界时,无需diff[j - 1 + 1] -=3
for(int i = 0;i < bookings.length;i++){
diff[bookings[i][0] - 1] += bookings[i][2];
if(bookings[i][1] != n){
diff[bookings[i][1]] -= bookings[i][2];
}
}
//3.换原差分数组
int[] result = new int[n];
result[0] = diff[0];
for(int i = 1;i < n;i++){
result[i] = result[i - 1] + diff[i];
}
return result;
针对diff差分数组扩充零元素以避免边界问题讨论:
//差分数组技巧适⽤于频繁对数组区间进⾏增减的场景。
int[] nums = new int[n];
for(int i = 0;i < n;i++){
nums[i] = 0;
}
//1.构造差分数组
int[] diff = new int[n + 1];
diff[0] = nums[0];
diff[n] = 0;
for(int i = 1;i < n;i++){
diff[i] = nums[i] - nums[i - 1];
}
//2.进⾏区间增减,i-j区间是针对数组的i-1,j-1之间,且j = n时,即为数组最右边界时,无需diff[j - 1 + 1] -=3
for(int i = 0;i < bookings.length;i++){
diff[bookings[i][0] - 1] += bookings[i][2];
diff[bookings[i][1]] -= bookings[i][2];
}
//3.换原差分数组
int[] result = new int[n];
result[0] = diff[0];
for(int i = 1;i < n;i++){
result[i] = result[i - 1] + diff[i];
}
return result;
370.区间加法
// //暴力遍历
// int[] result = new int[length];
// for(int i = 0;i < length;i++){
// result[i] = 0;
// }
// for(int i = 0;i < updates.length;i++){
// for(int j = updates[i][0];j <= updates[i][1];j++){
// result[j] += updates[i][2];
// }
// }
// return result;
差分法
int[] nums = new int[length];
for(int i = 0;i < length;i++){
nums[i] = 0;
}
//用扩充差分数组右侧零元素来解决边界冲突问题
//构造差分数组
int[] diff = new int[length + 1];
diff[0] = nums[0];
diff[length] = 0;
for(int i = 1;i < length;i++){
diff[i] = nums[i];
}
//区间增减
for(int i = 0;i < updates.length;i++){
diff[updates[i][0]] += updates[i][2];
diff[updates[i][1] + 1] -= updates[i][2];
}
//差分数组换原
nums[0] = diff[0];
for(int i = 1;i < length;i++){
nums[i] = nums[i - 1] + diff[i];
}
return nums;
1094.拼⻋
//本题将题目意思转换之后就是区间增减问题,但是细节注意是本题上下车站点i-j,则在区间上应该是区间[i,j - 1]内的增减
//所以编写程序需要注意,同时仍需注意diff有边界问题,扩充零元素]
默认初始化就是全零
int[] nums = new int[1000];
// nums[0] = 0;
// for(int i = 0;i < 1000;i++){
// nums[i] = 0;
// }
// 默认初始化就是全零
//本题将题目意思转换之后就是区间增减问题,但是细节注意是本题上下车站点i-j,则在区间上应该是区间[i,j - 1]内的增减
//所以编写程序需要注意,同时仍需注意diff有边界问题,扩充零元素
int[] diff = new int[1001];
diff[0] = nums[0];
// diff[1000] = 0;
for(int i = 1;i < 1000;i++){
diff[i] = nums[i] - nums[i - 1];
}
for(int i = 0;i < trips.length;i++){
diff[trips[i][1]] += trips[i][0];
diff[trips[i][2]] -= trips[i][0]; //这里就是本题的区别
}
//换原
nums[0] = diff[0];
if(nums[0] > capacity){
return false;
}
for(int i = 1;i < 1000;i++){
nums[i] = diff[i] + nums[i - 1];
if(nums[i] > capacity){
return false;
}
}
return true;
补充:以上代码中的全零数组创建不用初始化,因为默认初始化就是全零
队列/栈算法
对于括号匹配的题目,常用的做法是使用栈进行匹配,栈具有后进先出的特点,因此可以保证右括号和最近的左括号进行匹配。
20.有效的括号
//1.栈的压入与推出
// if((s.length() & 1) == 1) return false;//字符串长度为奇数则直接返回false
// Stack<String> stack = new Stack();
// char[] cstring = s.toCharArray();
// String sc = "";
// for(int i = 0;i < cstring.length;i++){
// sc = String.valueOf(cstring[i]);
// if("(".equals(sc) || "[".equals(sc) || "{".equals(sc)){
// stack.push(sc);
// }else if(stack.isEmpty()){
// return false;
// }else if(")".equals(sc)){
// if(!stack.pop().equals("(")) return false;
// }else if("]".equals(sc)){
// if(!stack.pop().equals("[")) return false;
// }else{
// if(!stack.pop().equals("{")) return false;
// }
// }
// if(!stack.isEmpty()) return false;
// return true;
//2.利用哈希表来快速获取对应括号
Map<Character, Character> map = new HashMap<Character, Character>() {{
put(')', '(');
put(']', '[');
put('}', '{');
}};
Stack<Character> stack = new Stack();
Character c;
for(int i = 0;i < s.length();i++){
c = s.charAt(i);
if(map.containsKey(c)){
if(stack.isEmpty() || stack.pop() != map.get(c)) return false;
}else{
stack.push(c);
}
}
if(!stack.isEmpty()) return false;
return true;
921.使括号有效的最少添加
以左括号为基准,通过维护对右括号的需求数 need,来计算最⼩的插⼊次数。
遍历字符串,通过⼀个 need 变量记录对右括号的需求数,根据 need 的变化来判断是否需要插⼊。当 need == -1 时,意味着我们遇到⼀个多余的右括号,显然需要插⼊⼀个左括号。
//通过⼀个 need 变量记录对右括号的需求数,根据 need 的变化来判断是否需要插⼊。当 need == -1 时,意味着我们遇到⼀个多余的右括号,显然需要插⼊⼀个左括号。
int need = 0,result = 0;
Character c;
for(int i = 0;i < s.length();i++){
c = s.charAt(i);
if(c == '('){
need++;
}else{
need--;
}
if(need == -1){
need++;
result++;
}
}
return result + need;
上面方法更快速,
用栈也能解决,但性能不足够好。
//是以左括号为基准,通过维护对右括号的需求数 need,来计算最⼩的插⼊次数。
Stack<Character> stack = new Stack();
for(int i = 0;i < s.length();i++){
if('(' == (s.charAt(i))){
stack.push('(');
}else{
if(!stack.isEmpty() && '(' == (stack.peek())){
stack.pop();
}else{
stack.push(')');
}
}
}
int result = 0;
while(!stack.isEmpty()){
stack.pop();
result++;
}
return result;
1541.平衡括号字符串的最少插入次数
遍历字符串,通过⼀个 need 变量记录对右括号的需求数,根据 need 的变化来判断是否需要插⼊。 类似 921. 使括号有效的最少添加,当 need == -1 时,意味着我们遇到⼀个多余的右括号,显然需要插⼊ ⼀个左括号。
对于本题,另外,当遇到左括号时,若对右括号的需求量为奇数,需要插⼊ 1 个右括号,因为⼀个左括号需要两个右括 号嘛,右括号的需求必须是偶数,这⼀点也是本题的难点。
//
int need = 0,result = 0;
Character c;
for(int i = 0;i < s.length();i++){
c = s.charAt(i);
if(c == '('){
need += 2;
if(need % 2 == 1){
//如果对右括号的需求为奇数,而我们需要左右1:2,所以需要添加一个左括号
result++;
need--;
}
}else{
need--;
if(need == -1){
//对右括号需求为-1,则添加一个左括号,那么对右括号需求就为1了
result++;
need = 1;
}
}
}
return result + need;
225.用队列实现栈
入栈操作时,首先将元素入队到 q2,然后将q1的全部元素依次出队并入队到q2,此时q2的前端的元素即为新入栈的元素,再将q1
和q2互换,则q1的元素即为栈内的元素,q1的前端和后端分别对应栈顶和栈底。
public void push(int x) {
q1.offer(x);
while(!q2.isEmpty()){
q1.offer(q2.poll());
}
LinkedList temp = new LinkedList();
temp = q1;
q1 = q2;
q2 = temp;
}
public LinkedList<Integer> q1;//q1作为临时队列使用
public LinkedList<Integer> q2;//q2作为实现栈
public MyStack() {
q1 = new LinkedList();
q2 = new LinkedList();
}
public void push(int x) {
q1.offer(x);
while(!q2.isEmpty()){
q1.offer(q2.poll());
}
LinkedList<Integer> temp = new LinkedList();
temp = q1;
q1 = q2;
q2 = temp;
}
public int pop() {
return q2.poll();
}
public int top() {
return q2.peek();
}
public boolean empty() {
return q2.isEmpty();
}
232.用栈来实现队列
设置一个栈为stackin,另一个栈为stackout。用俩个栈的push,pop,peek,isEmpty来实现队列的四个功能
public int pop() {
if(outStack.isEmpty()){
inToout();
}
return outStack.pop();
}
public void inToout(){
while(!inStack.isEmpty()){
outStack.push(inStack.pop());
}
}
class MyQueue {
private static Stack<Integer> inStack;
private static Stack<Integer> outStack;
public MyQueue() {
inStack = new Stack<Integer> ();
outStack = new Stack<Integer> ();
}
public void push(int x) {
inStack.push(x);
}
public int pop() {
if(outStack.isEmpty()){
inToout();
}
return outStack.pop();
}
public int peek() {
if(outStack.isEmpty()){
inToout();
}
return outStack.peek();
}
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
public void inToout(){
while(!inStack.isEmpty()){
outStack.push(inStack.pop());
}
}
二叉堆
堆的实现是数组,从数组索引1处开始;
构造堆:从length/2(最后一个非叶节点)倒叙至1执行下沉操作。
add:加至堆尾,执行上浮操作
deletemax:删除1处max,将N处元素置于1处执行下沉操作;
堆排序:1处max与N处对调,执行1处下沉操作,循环对调和下沉直至N’为2对调即可。
用堆实现最大/小优先队列,
可用API:
PriorityQueue pq = new PriorityQueue(); // 该API是最小优先队列
pq.add(num)
pq.size()
pq.poll()
pq.peek()
215.数组中的第K个最大元素
使用工具类:
PriorityQueue<Integer> pq = new PriorityQueue(); // 构造最小优先队列
for(int num : nums){
pq.add(num);
}
while(pq.size() > k){
pq.poll();
}
return pq.peek();
手动构造堆和优先队列:
//手动构造堆
int len = nums.length;
int[] arr = new int[len + 1];
for(int i = 0;i < len;i++){
arr[i + 1] = nums[i];
}
for(int i = len / 2;i > 0;i--){
down(arr,i,arr.length - 1);
}
//最大优先队列删除顶端max,
int maxk = 0;
for(int i = 0;i < k;i++){
maxk = arr[1];
exchange(arr,1,len - i);//1与N'交换
down(arr,1,len - i - 1);//下沉至N' - 1;
}
return maxk;
}
//下沉
public void down(int[] arr,int k,int indexlen){
// int indexlen = arr.length - 1;
int max;
while(2 * k <= indexlen){
if(2 * k < indexlen){
max = (arr[2 * k] > arr[2 * k + 1] ? 2 * k : 2 * k + 1);//获取俩者较大值的索引
if(arr[k] < arr[max]){
exchange(arr,k,max);
k = max;
}else{
return;
}
}else{
if(arr[k] < arr[2 * k]){
exchange(arr,k,2 * k);
k = 2 * k;
}else{
return;
}
}
}
return;
}
//元素交换
public void exchange(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
return;
}
703.数据流中的第 K 大元素
使用工具类:
public PriorityQueue<Integer> pq;
public int k;
public KthLargest(int k, int[] nums) {
this.k = k;
pq = new PriorityQueue(); // 该API是最小优先队列
for(int num:nums){
pq.add(num);
}
}
public int add(int val) {
pq.add(val);
while(pq.size() > this.k){
pq.poll();
}
return pq.peek();
}
手动构造堆和优先队列:
//手动构造堆
this.len = nums.length;
this.k = k;
for(int i = 0;i < len;i++){
arr[i + 1] = nums[i];
}
for(int i = len / 2;i > 0;i--){
down(arr,i,len);
}
}
public int add(int val) {
arr[++len] = val;
up();
for(int i = 1;i <= len;i++){
arrtemp[i] = arr[i];
}
//最大优先队列删除顶端max,
int maxk = 0;
for(int i = 0;i < k;i++){
maxk = arrtemp[1];
exchange(arrtemp,1,len - i);//1与N'交换
down(arrtemp,1,len - i - 1);//下沉至N' - 1;
}
return maxk;
}
//上浮
public void up(){
int m = len;
while(m > 1){
if(arr[m] >= arr[m / 2]){
exchange(arr,m,m / 2);
m = m / 2;
}else{
return;
}
}
}
//下沉
public void down(int[] arrd,int m,int indexlen){
// int indexlen = arr.length - 1;
int max;
while(2 * m <= indexlen){
if(2 * m < indexlen){
max = (arrd[2 * m] >= arrd[2 * m + 1] ? 2 * m : 2 * m + 1);//获取俩者较大值的索引
if(arrd[m] < arrd[max]){
exchange(arrd,m,max);
m = max;
}else{
return;
}
}else{
if(arrd[m] < arrd[2 * m]){
exchange(arrd,m,2 * m);
m = 2 * m;
}else{
return;
}
}
}
return;
}
//元素交换
public void exchange(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
return;
}