刷算法没有思路怎么办 ?
把常见的数据结构和算法思想过一遍。
常用的数据结构有数组、链表、队、栈、Hash、集合、树、堆。
常用的算法思想有查找、排序、双指针、递归、迭代、分治、贪心、回溯和动态规划等等。
剑指offer52
一定要好好读题目 !
遍历其中一个链表, 放入到 Set 、Hash 中, 在边遍历便从集合之中取出元素进行比较
/**
* 方法1:通过Hash辅助查找
*
* @param pHead1
* @param pHead2
* @return
*/
public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) {
return null;
}
ListNode current1 = pHead1;
ListNode current2 = pHead2;
Map<ListNode, Integer> map = new HashMap<>();
while (current1 != null) {
map.put(current1, null);
current1 = current1.next;
}
while (current2 != null) {
if (map.containsKey(current2)) {
return current2;
}
current2 = current2.next;
}
return null;
}
/**
* 方法2:通过集合来辅助查找
*
* @param headA
* @param headB
* @return
*/
public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
Set<ListNode> set = new HashSet<>();
while (headA != null) {
set.add(headA);
headA = headA.next;
}
while (headB != null) {
if (set.contains(headB)) {
return headB;
}
headB = headB.next;
}
不太了解栈的,可以看看 hello算法的介绍(里面也有相关方法的介绍)
最主要的特征就是先进后出 (PS 像极了我食堂买饭)
stackA.peek() == stackB.peek() 为什么需要满足这个条件才能取出栈顶元素 ?
原因 : 假设存在公共节点,那么最后面的一定相同的系节点!如果不是相同的系节点那么就不存在公共节点,互相矛盾了。当条件村成立时,就表示找到了第一个开始相同的子节点,直接返回即可
/**
* 方法3:通过栈
*/
public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
Stack<ListNode> stackA = new Stack();
Stack<ListNode> stackB = new Stack();
while (headA != null) {
stackA.push(headA);
headA = headA.next;
}
while (headB != null) {
stackB.push(headB);
headB = headB.next;
}
ListNode node = null;
while ((stackA.size() > 0) && (stackB.size() > 0)) {
// 注意这里时 peek() 方法而不是 pop()
// peek() 只是访问栈顶元素而不取出
if (stackA.peek() == stackB.peek()) {
node = stackA.pop();
stackB.pop();
} else {
// 返回
break;
}
}
return node;
}
/**
* 方法4:通过序列拼接
*/
public static ListNode findFirstCommonNodeByCombine(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) {
return null;
}
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
if (p1 != p2) {
if (p1 == null) {
p1 = pHead2;
}
if (p2 == null) {
p2 = pHead1;
}
}
}
return p1;
}
为什么公共子节点,不会在某一个长的链表前面?
应为你没读题(ps 说我自己)。如果出现在前面,那么两个链表的长度一定不可能不相同 !
/**
* 方法5:通过差值来实现
*
* @param pHead1
* @param pHead2
* @return
*/
public static ListNode findFirstCommonNodeBySub(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) {
return null;
}
ListNode current1 = pHead1;
ListNode current2 = pHead2;
int l1 = 0, l2 = 0;
while (current1 != null) {
current1 = current1.next;
l1++;
}
while (current2 != null) {
current2 = current2.next;
l2++;
}
current1 = pHead1;
current2 = pHead2;
// 这里还可以使用相关方法,比如去绝对值
int sub = l1 > l2 ? l1 - l2 : l2 - l1;
if (l1 > l2) {
int a = 0;
while (a < sub) {
current1 = current1.next;
a++;
}
}
if (l1 < l2) {
int a = 0;
while (a < sub) {
current2 = current2.next;
a++;
}
}
while (current2 != current1) {
current2 = current2.next;
current1 = current1.next;
}
return current1;
}
题目 LeetCode234
思路:
/**
* 使用栈进行判断是否为回文链表
*/
public boolean isPalindrome(ListNode head) {
// 创建一个 栈
Stack<ListNode> stack = new Stack<>();
ListNode current = head;
while (current != null) {
stack.push(current);
current = current.next;
}
current = head;
while (current != null) {
if (current.val != stack.pop().val) {
return false;
}
current = current.next;
}
return true;
}
实现优化
/**
* 方法3:只将一半的数据压栈
*
* @param head
* @return
*/
public static boolean palindromeByHalfStack(ListNode head) {
if (head == null) {
return true;
}
Stack<ListNode> stack = new Stack<>();
int len = 0;
ListNode current = head;
// 把链表节点的值存放到栈中
while (current != null) {
stack.push(current);
current = current.next;
len++;
}
// 除以 2
len >>= 1;
current = head;
while (len-- > 0) {
if (stack.pop().val != current.val) {
return false;
}
current = current.next;
}
return true;
}
LeetCod21 将两个升序链表合并为一个新的升序链表并返回,新链表是通过拼接给定的两个链表的所有节点组成的。
public static ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// write code here
ListNode newHead = new ListNode(-1);
ListNode res = newHead;
while (list1 != null || list2 != null) {
if (list1 != null && list2 != null) {//都不为空的情况
if (list1.val < list2.val) {
newHead.next = list1;
list1 = list1.next;
} else if (list1.val > list2.val) {
newHead.next = list2;
list2 = list2.next;
} else { //相等的情况,分别接两个链
newHead.next = list2;
list2 = list2.next;
newHead = newHead.next;
newHead.next = list1;
list1 = list1.next;
}
// 公共部分提取出来
newHead = newHead.next;
} else if (list1 != null && list2 == null) {
newHead.next = list1;
list1 = list1.next;
newHead = newHead.next;
} else if (list1 == null && list2 != null) {
newHead.next = list2;
list2 = list2.next;
newHead = newHead.next;
}
}
return res.next;
}
优化一下代码
public static ListNode mergeTwoLists2(ListNode list1, ListNode list2) {
// write code here
ListNode newHead = new ListNode(-1);
ListNode res = newHead;
while (list1 != null && list2 != null) {
if (list1.val > list2.val) {
res.next = list2;
list2 = list2.next;
} else if (list2.val > list1.val) {
res.next = list1;
list1 = list1.next;
} else {
// list2.val == list1.val
res.next = list1;
res.next = list2;
list1 = list1.next;
list2 = list2.next;
}
res = res.next;
}
while (list1 != null && list2 == null) {
res.next = list1;
list1 = list1.next;
res = res.next;
}
while (list1 == null && list2 != null) {
res.next = list2;
list2 = list2.next;
res = res.next;
}
return newHead.next;
}
1669. 合并两个链表
给你两个链表 list1
和 list2
,它们包含的元素分别为 n
个和 m
个。
请你将 list1 中下标从 a
到 b
的全部节点都删除,并将list2 接在被删除节点的位置。
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
// 注意这边有两个指向的是同一个人链表
ListNode pre1 = list1, post1 = list1, post2 = list2;
int i = 0, j = 0;
while (pre1 != null && post1 != null && j < b) {
if (i < a - 1) {
pre1 = pre1.next;
i++;
}
if (j != b) {
post1 = post1.next;
j++;
}
}
while (post2.next != null) {
post2 = post2.next;
}
// 连接相关的节点
// 下面有图片解析
pre1.next = list2;
post2.next = post1.next;
return list1;
}
LeetCode876
给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
定义连个指针, 一个 slow 另一个是 fast
public static ListNode middleNode(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
剑指 Offer 22. 链表中倒数第k个节点
需要提前把 fast 指针指向第 k + 1
位置, slow 指针就在第一个位置就 ok。当 fast 到达了 k + 1 位置,那么两个指针就一起向后遍历
ps 也可以到 第 k 个位置,那么后面 fast 指针需要到达的位置就是 最后以一个元素
结束时两个指针的位置
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast = head;
ListNode slow = head;
// 这里有可能会出现 k 大于 链表长度的情况
while (fast != null && k > 0) {
fast = fast.next;
k--;
}
while (fast != null) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
双指针,需要执行到图示位置。
public ListNode rotateRight(ListNode head, int k) {
if (head == null || k == 0) {
return head;
}
ListNode temp = head;
ListNode fast = head;
ListNode slow = head;
int len = 0;
while (head != null) {
head = head.next;
len++;
}
// 如果这个条件成立那么就表示不需要进行反转,比如 k = 0 或这 k = len ....
if (k % len == 0) {
return temp;
}
while ((k % len) > 0) {
fast = fast.next;
k--;
}
// 之后 fast 来到了第 k + 1 的位置
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
// 两个指针都到了该到的位置
ListNode res = slow.next;
slow.next = null;
fast.next = temp;
return res;
}
删除的位置无非就是 链表的头节点,链表的其他位置节点
头节点
来说, 由于其特殊性(没了整个链表都没了)所以需要一个虚拟的头节点
dummyHead public ListNode removeElements(ListNode head, int val) {
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
ListNode current = dummyHead;
while (current.next != null) {
if (current.next.val == val) {
current.next = current.next.next;
} else {
current = current.next;
}
}
return dummyHead.next;
}