代码随想录算法训练营day04 | leetcode

2023-08-01,,

基础知识

记录一下栈实现及操作

public class ArrayDequeStack
{
public void main()
{
ArrayDeque stack = new ArrayDeque();
// 大小
System.out.println(stack.size());
// 依次将三个元素push入"栈"
stack.push("循循渐进Linux");
stack.push("小学语文");
stack.push("时间简史");
// 输出:[时间简史, 小学语文, 循循渐进Linux]
System.out.println(stack);
// 访问第一个元素,但并不将其pop出"栈",输出:时间简史
System.out.println(stack.peek());
// 依然输出:[时间简史, 小学语文, 循循渐进Linux]
System.out.println(stack);
// pop出第一个元素,输出:时间简史
System.out.println(stack.pop());
// 输出:[小学语文, 循循渐进Linux]
System.out.println(stack);
}
}

记录一下Map常用操作

HashMap<String, String > myMap  = new HashMap<String, String>(){{
put("a","b");
put("b","b");
}}; {
Object object = new Object();
Object key = new Object();
Object value = new Object();
Map map1 = new HashMap<>();
Map map2 = new TreeMap();
Map map3 = new LinkedHashMap();
map1.get(key); map1.put("string", object);
map1.putIfAbsent(1, object); map1.remove(key); map1.isEmpty();
map1.containsKey(key);
map1.containsValue(value);
map1.getOrDefault(13, 0);
map1.replace(key, value, value); // 存不存在key 或 value
// 如果指定的键尚未与某个值相关联(或映射到 null )将其与给定值相关联并返回 null ,否则返回当前值。
for (Object key1 : map1.keySet()) {
Object value1 = map1.get(key);
System.out.println(key1 + " = " + value1);
} // 根据泛型的类型判断该如何进行排序 直接根据key 或者 根据key获取value进行lambda比较器排序
Map<Character, Integer> map = new HashMap<Character, Integer>();
List<Character> list = new ArrayList<Character>(map.keySet());
// 按value 降序排序
Collections.sort(list, (a, b) -> map.get(b) - map.get(a));
// 按key 降序排序
Collections.sort(list, (a, b) -> b.compareTo(a));
}

LeetCode 24

分析1.0

交换相邻的两个节点,需要两个指针 pre p,不修改节点内部的值,意味着只改变原节点next指向,

    空链 或链中只有一个节点 直接返回
    ans指向 第一对交换后的相邻节点左侧
    循环终止条件只剩下0或1个元素,可退出终止

失误

    只考虑到了仅有两个元素的交换,如果有3个以上元素的交换,还需要设置last指针指向已交换完成的链表段的最后一个元素
    应将链表分为三部分 ①已操作好链表段 last指向这段尾节点 ②待交换的2个节点③剩余链表段 first指向这段首节点

    if(p1.next == null || p1 == null) 就近原则可能报错

class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode ans = new ListNode(-1);
ListNode p1 = head, p2 = head.next, first, last = new ListNode(-2);
// 至少有两个节点
while(true){
first = p2.next;
p2.next = p1;
p1.next = first;
last.next = p2;
last = p1;
if(ans.val == -1){
ans = p2;
}
if(first == null){
break;
}
p1 = first;
p2 = p1.next;
if(p1== null || p2 == null){
break;
}
}
return ans;
}
}

分析2.0

应将链表分为三部分 ①已操作好链表段 last指向这段尾节点 ②待交换的2个节点③剩余链表段 first指向这段首节点

class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode ans = new ListNode(-1);
ListNode p1 = head, p2 = head.next, first = p2.next, last = new ListNode(-1,head);
// 至少有两个节点
while(true){
// last.next 指向p2 p2指向p1 p1指向first
last.next = p2;
p2.next = p1;
p1.next = first;
last = p1;
if(ans.val == -1){
ans = p2;
}
p1 = last.next;
if(p1 == null || p1.next == null){
break;
}
p2 = p1.next;
first = p2.next;
}
return ans;
}
}

LeetCode 19

分析1.0

要求删除链表的倒数第n个节点 思路:设置两个指针,p pre, 间隔n个元素,当p = null 时pre.next = 待删节点

失误 遇到一个奇怪的点 pre.next = head pre.next=pre.next.next head竟然没变

订正 确实是没变

class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head == null){
return null;
}
// p最终要指向的是最后一个节点 所以循环终止条件是p.next != null
ListNode pre = new ListNode(101,head), p = head;
int cnt = 1;
while(cnt < n && p != null){
p = p.next;
cnt++;
}// 遍历结束时,p指针指向第n个节点 pre.next = head
System.out.println("p----"+p.val);
System.out.println("pre----"+pre.val);
while(p.next != null){
System.out.println("循环");
p = p.next;
pre = pre.next;
}
// pre指向倒数第n+1个节点
System.out.println(pre.next == head);
pre.next = pre.next.next;
System.out.println(pre.next == head);
System.out.println("head----"+head.val);
System.out.println("pre.next----"+pre.next);
return head;
}
}

修改 返回虚拟头结点的next

class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head == null){
return null;
}
// p最终要指向的是最后一个节点 所以循环终止条件是p.next != null
ListNode pre = new ListNode(101,head), p = head, virtualNode = pre;
int cnt = 1;
while(cnt < n && p != null){
p = p.next;
cnt++;
}// 遍历结束时,p指针指向第n个节点 pre.next = head
while(p.next != null){
p = p.next;
pre = pre.next;
}
// pre指向倒数第n+1个节点
pre.next = pre.next.next;
return virtualNode.next;
}
}

分析2.0

p可走n+1步后,pre接着走,这样pre直接就指向了待删除元素的前一个

LeetCode 面试题 02.07. 链表相交

分析1.0

这题一看过去首先想到的就是要逆向遍历,但是又要求不能改变原来链表的结构,便用两个list装下所有数据完成转置,接着开始遍历两个list,找到最后一个相等节点,

public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null){
return null;
}
ListNode p1 = new ListNode(-1,headA), p2 = new ListNode(-2,headB);
List<ListNode> list1 = new LinkedList(), list2 = new LinkedList();
while(p1.next != null){
list1.add(p1.next);
p1 = p1.next;
}
while(p2.next != null){
list2.add(p2.next);
p2 = p2.next;
} /*if(headA.val != headB.val){
System.out.println("尾节点不同");
return null; // headA headB是始终不变的这么比比的是原链表的首节点
}*/
// 通过日志发现了问题 压根儿就没有转置
for(int i = 0; i < list1.size(); i++){
System.out.println(list1.get(i).val);
} int cnt = -1;
while(list1.get(cnt+1) == list2.get(cnt+1) && list1.get(cnt+1) != null && list2.get(cnt+1) != null){
System.out.println(list1.get(cnt+1));
cnt++;
}
return list1.get(cnt);
}
}

失误:真是闹了个乌龙

分析2.0

直接插入采用的是尾插法,根本就没有实现元素转置 转置可用栈 可惜超出内存限制了

public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null){
return null;
}
ListNode p1 = new ListNode(-1,headA), p2 = new ListNode(-2,headB);
ArrayDeque<ListNode> stack1 = new ArrayDeque(), stack2 = new ArrayDeque();
while(p1.next != null){
stack1.push(p1.next);
}
while(p2.next != null){
stack2.push(p2.next);
}
// 开始比较选出首个不等节点
ListNode ans = null;
while(true){
p1 = stack1.pop();
p2 = stack2.pop();
if(p1.val != p2.val){
break;
}
ans = p1;
}
return ans;
}
}

分析3.0

看了卡哥的思路,发现自己还是没有准确理解节点相等这个要求

将链表以当前遍历节点为基准分为三段,若A B指向了相同的节点,那之后的那一段一定是相同的 而比较节点是否相同比较的也是ListNode这个引用是否相等(和数组的区别之一)

于是① 获取A B长度,移动长链表头指针headA至以新的headA为头结点的链表长度等于原短链表的长度,开始比较,节点引用相等的点为所求

public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0, lenB = 0;
while (curA != null) { // 求链表A的长度
lenA++;
curA = curA.next;
}
while (curB != null) { // 求链表B的长度
lenB++;
curB = curB.next;
}
curA = headA;
curB = headB;
// 让curA为最长链表的头,lenA为其长度
if (lenB > lenA) {
//1. swap (lenA, lenB);
int tmpLen = lenA;
lenA = lenB;
lenB = tmpLen;
//2. swap (curA, curB);
ListNode tmpNode = curA;
curA = curB;
curB = tmpNode;
}
// 求长度差
int gap = lenA - lenB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap-- > 0) {
curA = curA.next;
}
// 遍历curA 和 curB,遇到相同则直接返回
while (curA != null) {
if (curA == curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
} }

分析4.0

    抽象思维 链表分3段
    明确所求结果落实到链表状态上是个什么情形
    从结果倒推循环起点

LeetCode 142

分析1.0 

从图的出入度考虑,入环节点入度为2,简历<ListNode,<index,int>>哈希表,访问到某一节点时,入度+1,如果当前节点入度为2,返回当前节点index,若节点为null,return -1

但是这样会报错

HashSet<ListNode, <Integer,Integer>> set = new HashSet();

好吧 分析1.0都是错的

订正

要使用的结构为Map 

public class Solution {
public ListNode detectCycle(ListNode head) {
HashMap<ListNode, Integer> map = new HashMap();// 节点,索引
ListNode cur = head;
int index = 0;
while(true){
if(cur == null){
return null;
}
if(map.containsKey(cur)){
return cur;
}else{
map.put(cur,index++);
}
cur = cur.next;
}
}
}

分析2.0

这题其实用不着记录入度,用Set<ListNode> 就行,遍历时查重即可

分析3.0

空间复杂度为O(1) 考虑双指针 滑动窗口这类思想

快慢指针 a指针一次走两步,b指针一次走一步,相当于a指针一次走一步b指针没动 这个思路Mark一下

public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {// 有环
ListNode index1 = fast;
ListNode index2 = head;
// 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}

参考卡哥 https://programmercarl.com/0142.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8II.html

总结

    总结一个做题抽象思维,把数据看成几个整体,整体之间如何联系、整体之内如何联系
    if(p1.next == null || p1 == null) 就近原则可能报错

    ListNode pre = new ListNode(5,head) 自定义val值时考虑题目限制 我这里设为负数被系统强制改为绝对值

    链表题考虑虚拟头结点!!!
    pre.next = head pre.next=pre.next.next head是不会变的,一直指向那块内存区域 而链表的结构是发生了变化的
    搞清循环终止条件是  p != null 还是 p.next !=null 计数器定位可采取特殊法
    删除节点定位它的前一个节点
    特殊情况一定要优先考虑,有时候不光考虑一个结构有无元素 返回的结果要留意是否为null
    循环是重点 循环的其实条件 终止条件 注意循环前变量值循环后变量值
    快慢指针 a指针一次走两步,b指针一次走一步,相当于a指针一次走一步b指针没动

常用变量名增量更新

size、val、ans、cnt、cur、pre、next、left、right、index、gap

 

 
 

代码随想录算法训练营day04 | leetcode的相关教程结束。

《代码随想录算法训练营day04 | leetcode.doc》

下载本文的Word格式文档,以方便收藏与打印。