704. 二分查找
二分查找的前提是数组为有序数组,同时强调数组中无重复元素
左闭右闭
循环条件while(left <= right)
要使用 <=
,同时,判断语句中下一个left
orright
赋值为下一个,因为在当前的闭区间里是一定不包含要查找的元素
左闭右开
循环条件while(left < right)
要使用<
,同时,由于右开所以判断下一个right
有可能在区间内,所以当nums[mid] > target
时right = mid
27. 移除元素
由于只需要返回数组的新长度,所以找到不等于val的值置前并统计个数返回即可
977. 有序数组的平方
使用内置函数
题目要求按非递减顺序排序,对数组中的所有值进行平方,然后Arrays.sort(nums)
排序返回。其空间复杂度为O(logn),n为数组的长度。
双指针
同样地,我们可以使用两个指针分别指向位置 0 和 n−1,每次比较两个指针对应的数,选择较大的那个逆序放入答案并移动指针。需要注意的是循环终止条件while(left <= right)
209. 长度最小的子数组
使用滑动窗口,需要注意的是循环条件为右指针,左指针通过sum
与target
的值来进行变化。如果循环条件为左指针,方法无问题,但是会超出时间限制。
59. 螺旋矩阵 II
模拟矩阵的填入,定义上下左右四个边界初始值,通过四个循环填入。当每一行/列遍历结束,则其边界条件随即变化,防止重复遍历的问题。
class Solution {
public int[][] generateMatrix(int n) {
int [][] cons = new int [n][n];
int l=0,r=n-1,t=0,b=n-1;
int temp = n * n,s = 0;
while(s < temp){
for(int i = l;i <= r;i++){// 从左至右,i为左边界,终止条件为右边界
cons[t][i] = ++s;
}
t++;//上边界加1
for(int i = t;i <= b;i++){// 从上至下
cons[i][r] = ++s;
}
r--;//右边界加1
for(int i = r;i >= l;i--){// 从右至左
cons[b][i] = ++s;
}
b--;//下边界加1
for(int i = b;i >= t;i--){// 从下至上
cons[i][l] = ++s;
}
l++;//左边界加1
}
return cons;
}
}
链表分为单链表、双链表、循环链表,链表在内存中的存储不是连续分布的,俄日是散乱分布在内存中的某地址上;
链表的定义
public class ListNode{
int val; // 结点的值
ListNode next; // 下一个结点
public ListNode(){ // 结点的构造函数(无参)
}
public ListNode(int val){ // 结点的构造函数(有一个参数)
this.val = val;
}
public ListNode(int val,ListNode next){ // 结点的构造函数(有两个参数)
this.val = val;
this.next = next;
}
}
203. 移除链表元素
添加虚拟头结点
ListNode dummy = new ListNode(-1,head)
创建一个新的结点,赋值为-1next结点为头结点,将虚拟结点下一个的结点也就是头结点赋给cur
进行遍历,这样就不会改变原先链表的顺序。
不添加虚拟头结点
需要对头结点单独考虑
递归
public class ListNode{
int val; // 结点的值
ListNode next; // 下一个结点
public ListNode(){ // 结点的构造函数(无参)
}
public ListNode(int val){ // 结点的构造函数(有一个参数)
this.val = val;
}
public ListNode(int val,ListNode next){ // 结点的构造函数(有两个参数)
this.val = val;
this.next = next;
}
}
707. 设计链表
206. 反转链表
设置虚拟头结点ListNode dummy = null
,需要注意的是要提前存储当前操作结点的下一个结点,因为当当前连接了前一个结点后,下一个结点无法获取到。
24. 两两交换链表中的节点
创建虚拟头结点
当要操作的第一个结点和第二个结点不为空时进行操作,先连接第2个结点,然后连接第1个结点,最后连接第3个结点:
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(-1,head); // 创建虚拟头结点
ListNode cur = dummy;
while(cur.next != null && cur.next.next != null){
ListNode temp = cur.next; // 存储第一个结点
ListNode prev = temp.next.next; // 存储第三个结点
cur.next = cur.next.next; // 连接第2个结点
cur.next.next = temp; // 连接第1个结点
temp.next = prev; // 连接第3个结点
cur = temp;
}
return dummy.next;
}
}
19. 删除链表的倒数第 N 个结点
快慢指针 + 虚拟头结点
在顺序结构中,删除倒数第N个结点,则需要找到倒数第N+1个结点,所以先让快指针走N+1次,然后快慢指针同时进行直到快指针值为null
:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(-1,head);
ListNode fast = dummy;
ListNode slow = dummy;
for(int i = 0 ; i <= n ; i++){
fast = fast.next;
}
while(fast != null){
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
计算链表长度
同样的,要删除倒数第N个结点,我们首先要找到倒数N+1的结点:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
int size = 0;
ListNode dummy = new ListNode(-1, head);
ListNode cur = dummy.next;
while(cur != null){
size ++; // 遍历计算长度
cur = cur.next;
}
cur = dummy;
int t = size - n;
for(int i = 0 ; i < t ; i++){
cur = cur.next; // 找到倒数N+1的结点
}
cur.next = cur.next.next;
return dummy.next;
}
}
面试题 02.07. 链表相交
双指针
如图,链表headA
遍历结束然后遍历headB
到交点的结点总数为a+(b-c),链表headB
遍历结束然后遍历headA
到交点的结点总数为b+(a-c),如果有公共尾部即c>0,则指向同一个结点curA
|curB
,如果没有公共尾部即c=0,则同时指向null
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode A = headA, B = headB;
while (A != B) {
A = A != null ? A.next : headB;
B = B != null ? B.next : headA;
}
return A;
}
}
142. 环形链表 II
哈希表
利用哈希表遍历每一个结点,如果遇到哈希表中已经存在的则说明链表有环且该结点为入环的第一个节点
public class Solution {
public ListNode detectCycle(ListNode head) {
Set set = new HashSet();
while(head != null){
if(set.contains(head)){ // 判断哈希表中是否存在当前结点
return head;
}else{
set.add(head);
head = head.next;
}
}
return null;
}
}
双指针
这类链表题目一般都是使用双指针法解决的,例如寻找距离尾部第K个节点、寻找环入口、寻找公共尾部入口等。
休息日
242. 有效的字母异位词
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法
数组
题目中说明字符串均由小写字母表示,因此可以用数组来记录充当哈希表;
HashMap
如果输入字符包含了unicode字符,则所需要的数组容量相对较大,因此使用两个HashMap分别记录两个字符中出现的字符和对应的次数,然后使用record1.equals(record2)
来判断两个HashMap是否相等。
几个经常用的Java HashMap方法:isEmpty()
判断是否为空;size()
计算HashMap中的键/值对的数量;put()
将键/值对添加到 HashMap 中;get()
获取指定 key 对应对 value;containsKey()
检查 hashMap 中是否存在指定的 key 对应的映射关系;containsValue()
检查 hashMap 中是否存在指定的 value 对应的映射关系;getOrDefault()
获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值;
349. 两个数组的交集
判断两个数组的交集,不需要统计其次数,因此使用HashSet对出现的数字进行统计即可,然后和第二个数组进行对比,将再次出现的数字(交集)存入新的HashSet中,遍历新的HashSet存入数组。
202. 快乐数
通过一系列计算,发现当不是快乐数时数字会陷入无限循环,因此使用HashSet对出现的数字进行记录,如果再次出现则说明不是快乐数。
1. 两数之和
进阶-使用复杂度O(1)的算法-HashMap
使用HashMap对数组进行遍历记录,Key记录值,Value记录值所对应的下标,通过计算差值寻找哈希表中是否存在需要的值,同时判断两者下标是否相同,如果不同则返回(有且仅有一解)
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] record = new int[2];
Map map = new HashMap<>();
for(int i = 0 ; i < nums.length ; i++){
map.put(nums[i], i);
}
for(int i = 0 ; i < nums.length ; i++){
int num = target - nums[i];
if(map.containsKey(num) && i != map.get(num)){
record[0] = i;
record[1] = map.get(num);
break;
}
}
return record;
}
}
454. 四数相加 II
题目中要求返回满足条件的元组个数并不要求返回对应下标,四数相加可以演变成两数相加来进行操作,统计nums1
和nums2
两个数组元素之和,和出现的次数,放到map中。然后遍历nums3
和nums4
查找是否存在满足条件的元素,如果有累加map中对应元素出现的次数。
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
int cnt = 0, n = nums1.length;
Map record1 = new HashMap();
for(int i = 0 ; i < n ; i++){
for(int j = 0 ; j < n ; j++){
record1.put(nums1[i] + nums2[j],record1.getOrDefault(nums1[i] + nums2[j],0)+1);
}
}
for(int i = 0 ; i < n ; i++){
for(int j = 0 ; j < n ; j++){
int sum = nums3[i] + nums4[j];
if(record1.containsKey(0-sum)){
cnt += record1.get(0-sum);
}
}
}
return cnt;
}
}
383. 赎金信
字符串均由小写英文字母组成,可以使用数组-哈希表,当有其他字符元素时可使用一个HashMap
★15. 三数之和
使用双指针,题目要求不可以包含重复的三元组,因此要注意去重
去重
a, b ,c, 对应的就是 nums[i],nums[left],nums[right]。
首先是a的去重,有两种方式,一种是与前一个判断,另一种是与后一个判断,如果选择与后一个判断的话,我们看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到 结果集里。因此要选择与前一个判断
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
然后是b与c的去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
class Solution {
public List> threeSum(int[] nums) {
List> result = new ArrayList<>();
Arrays.sort(nums);
int left = 0,right;
for(int i = 0 ; i < nums.length ; i++){
if(nums[i] > 0) return result;
if (i > 0 && nums[i] == nums[i - 1]) { // 去重a
continue;
}
left = i + 1;
right = nums.length - 1;
while(left < right){
int sum = nums[left] + nums[right];
if(nums[i] + sum == 0) {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
else if(nums[i] + sum > 0) right--;
else left++;
}
}
return result;
}
}
18. 四数之和
和15题三数之和类似,同样使用双指针,不同的两个点是:①两层循环②判断条件需要添加nums[i] > 0
其余均相同。