一、数组篇
1、基本用法
//定义一个一维数组,nums.length为数组长度
int[] ans = new int[nums.length];
//定义一个大小为m*n的二维数组
int[][] matrix = new int[m][n];
//获取行数
int lenY = matrix.length;
//获取列数---4列
int lenX = matrix[0].length;
//用sort函数对数组ans进行排序
Arrays.sort(ans);
//创建一个整数List集合
List<Integer> vals = new ArrayList<Integer>();
//把链表值复制到集合中
vals.add(currentNode.val);
//取数组最后一个元素的索引,size()方法为求集合长度
int back = vals.size() - 1;
//取数组某个索引的值
vals.get(front)//vals为定义的数组,front为索引下标
2、二分查找(折半查找)
为避免溢出,可以将middle = (left + right) / 2 改为 middle = (right - left) / 2 + left;
(1)区间左闭右闭
right = array.size() - 1;
两个临界条件:
left <= right
right = middle - 1 或 left = middle + 1
(2)区间左闭右开
right = array.size();
两个临界条件:
left < right
right = middle 或 left = middle + 1
3、移除元素
注意:由于数组在内存中存储的方式,所以数组中的元素不能删除,只能覆盖。
(1)库函数
erase()函数
时间复杂度:O(n)
(2)暴力法
两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组
(3)双指针(快慢指针)思路
时间复杂度:O(n)
定义两个指针:
快指针:指向新数组所需要的元素
慢指针:指向新数组的下标值
然后将快指针获取到的元素值赋给慢指针
4、长度最小的子数组
注:子数组默认是数组中连续的非空元素序列,但二维数组不是。
(1)暴力法
两层for循环
(2)双指针(滑动窗口)
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
for循环里的变量表示的是滑动窗口的终止位置
滑动窗口的关键是如何移动起始位置
具体思路看https://programmercarl.com/0209.%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.html#%E6%80%9D%E8%B7%AF
二、链表篇
哨兵结点在链表中用处很大,一定要记得考虑。
1、基础用法
初始化一个空节点,初始赋值为0,指针指向为list
ListNode list = new ListNode(0);
初始化一个空节点,初始赋值为0,并且list的下一个next指针指向head,指针指向为list
ListNode list = new ListNode(0,head);
定义一个空链表
ListNode list=null;
通常定义一个空节点还需要有节点的next指针指向,否则只是定义一个空节点
ListNode list = new ListNode(0,head);
//或者
ListNode list = new ListNode(0);
list.next=head;
虚拟头节点
//为链表head创建一个虚拟头结点
ListNode dummy=new ListNode(-1);
dummy.next=head;
ListNode p=dummy;
2、反转链表(P206)
双指针法
// 双指针
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode cur = head;
ListNode temp = null;
while (cur != null) {
temp = cur.next;// 保存下一个节点
cur.next = prev;
prev = cur;
cur = temp;
}
return prev;
}
}
递归方式
一般来说,当子问题和原问题具有相同的结构时,考虑自上而下的递归。如合并两个有序链表P21
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 链表为空时直接返回,链表不为空则到返回最后一个节点
if(!head || !head->next) {
return head;
}
// newHead先指向最后一个节点,注意此时参数是倒数第二个节点
// 这一步很精妙,每一次newHead都是指向空指针(链表为空)或保留在原链表中的最后一个节点(链表不空),作用就是返回新的头结点
ListNode* newHead = reverseList(head->next);
// 最后一个节点指向倒数第二个节点
head->next->next = head;
// 倒数第二个节点的下一节点置空。此时倒数第三个节点仍指向倒数第二个节点,下一次递归中将倒数第二个节点下一节点指向倒数第三个节点,不断重复这一过程
head->next = nullptr;
return newHead;
}
};
头插法
//使用头插法
ListNode dummy = new ListNode(0);//创建一个新结点,参数值为0代表链表为空
ListNode p = dummy, cur = head;
while(cur != null){
//从head摘下一个头
ListNode t = cur;
cur = cur.next; //cur移到下一个
t.next = p.next; //头插法插入
p.next = t;
}
return dummy.next;
迭代方式
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
}
3、常见双指针用法
(1)倒数第k个元素
设有两个指针 p 和 q,初始时均指向头结点。首先,先让 p 沿着 next 移动 k 次。此时,p 指向第 k+1个结点,q 指向头节点,两个指针的距离为 k 。然后,同时移动 p 和 q,直到 p 指向空,此时 q 即指向倒数第 k 个结点。
(2)获取中间元素
设有两个指针 fast 和 slow,初始时指向头节点。每次移动时,fast向后走两次,slow向后走一次,直到 fast 无法向后走两次。这使得在每轮移动之后。fast 和 slow 的距离就会增加一。设链表有 n 个元素,那么最多移动 n/2 轮。当 n 为奇数时,slow 恰好指向中间结点,当 n 为 偶数时,slow 恰好指向中间两个结点的靠前一个(可以考虑下如何使其指向后一个结点呢?)。
(3)是否存在环
总结快慢指针的特性 —— 每轮移动之后两者的距离会加一。
当一个链表有环时,快慢指针都会陷入环中进行无限次移动,然后变成了追及问题。想象一下在操场跑步的场景,只要一直跑下去,快的总会追上慢的。当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,同时快指针到慢指针的距离也减少一,只要一直移动下去,快指针总会追上慢指针。
如果存在环,如何判断环的长度呢?方法是,快慢指针相遇后继续移动,直到第二次相遇。两次相遇间的移动次数即为环的长度。
4、链表相加
简单来说,就是求两个链表交点节点的指针。
注意:交点不是数值相等,而是指针相等。
5、环形链表Ⅱ(P142)
出处:代码随想录
(1)判断链表是否有环
可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢
首先第一点:fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。
那么来看一下,为什么fast指针和slow指针一定会相遇呢?
可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。
会发现最终都是这种情况, 如下图:
fast和slow各自再走一步, fast和slow就相遇了
这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。
(2)如果有环,怎么找到这个环的入口
那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。