Skip to content

Commit

Permalink
0109
Browse files Browse the repository at this point in the history
  • Loading branch information
yk committed Jan 12, 2024
1 parent 9b1df07 commit 6cc7766
Show file tree
Hide file tree
Showing 3 changed files with 311 additions and 2 deletions.
2 changes: 1 addition & 1 deletion config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ auto = false
# 是否显示代码块的复制按钮
copy = true
# 默认展开显示的代码行数
maxShownLines = 30
maxShownLines = 50
# 表格配置
# [params.page.table]
# # 是否开启表格排序
Expand Down
2 changes: 1 addition & 1 deletion content/posts/2024/01/0104.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: '0104'
title: '0104--排序回顾&递归时间复杂度&异或技巧'
date: 2024-01-04T15:37:23+08:00
lastmod: 2024-01-08
tags: []
Expand Down
309 changes: 309 additions & 0 deletions content/posts/2024/01/0109.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
---
title: '0109'
date: 2024-01-09T20:10:19+08:00
lastmod:
tags: []
series: []
categories: []
---

## 哈希表

在 javascript 中的 Set 和 Map 咱就不说啥了,非正规军 😄。来看看 java 的:

1. 无序表
- HashSet: add remove contains,对呀 c++ 中的 unordered_set
- HashMap: put remove containsKey,对应 c++ 中的 unordered_map
2. 有序表
- TreeSet:它是按升序对元素进行排序,对应 c++ 中的 ordered_set
- TreeMap:基于红黑树的 NavigableMap 实现,它根据其键的自然顺序排序,对应 c++ 中的 ordered_map

记得之前学 java 的时候, HashSet 底层就是 HashMap,只不过没有 value 只有 key 罢了。

1. 放入无/有序表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
2. 放入无序表的东西,如果不是基础类型,内部按引用传递,内存占用的就是这个东西的内存地址的大小,java 中是 8 个字节
3. 放入有序表的东西,如果不是基础类型,**必须提供比较器**,内部按引用传递...。

> 重点:无序表 的增删改查的时间复杂度都是 **常数** 级别(比较大的常数 哈哈),有序表的时间复杂度都是 O(logn) 级别
---

## 链表

```java
class Node<V> {
V value;
Node next;
}
class Node<V> {
V value;
Node next;
Node last;
}
```

> 对于链表算法,在面试中,一定尽量用到空间复杂度最小的方法(不然凭啥用咱是吧 🐶)。
### 链表换头

当链表的操作会产生换头的情况时,函数的递归调用形式应该是 `head = func(head.next)`,所以函数在设计的时候就应该有一个 Node 类型的返回值,比如反转链表,见下方。

### 链表哨兵守卫

通过在链表头部添加一个哨兵节点(dummy node),这样就无需特殊处理**头节点为空**或者**头节点变化**的边界条件。

比如链表去头加尾操作:

```java
/* ---------- 头部删 ---------- */
public void delHead() {
ListNode p = head;
/**
* 没有守卫时,删除头节点的操作是 p = p.next
* if (p != null) {
* p = p.next;
* }
* 而删除其他的节点的操作是 p.next = p.next.next,
* 所以就需要分别讨论删除head和其它节点的情况
*
* 恰恰因为有 dummy 守卫节点的存在,无论头节点还是其他节点都统一使用 p.next = p.next.next
* if (p.next != null) {
* p.next = p.next.next;
* }
* 就简化了边界条件的判断
*/
}
```

再比如,需要创建一条新链表,见下方分离链表的操作。

<!-- public void deleteAtIndex(int index) {
ListNode p = head;
for (int i = 0; i < index; i++) {
p = p.next;
if (p == null) {
return; // 超出链表长度
}
}
if (p.next != null) {
p.next = p.next.next;
}
}
public void deleteAtIndex(int index) {
if (index < 0 || head == null) {
return;
}
if (index == 0) {
head = head.next;
return;
}
ListNode p = head;
for (int i = 0; i < index - 1; i++) {
p = p.next;
if (p == null) {
return; // 超出链表长度
}
}
if (p.next != null) {
p.next = p.next.next;
}
} -->

```java
/* ---------- 尾部增 ---------- */
public void addTrail(ListNode node) {
/**
* 当 head 为 null 时,在尾部添加node就需要处理head为null的边界条件
* if (head == null) {
* head = node;
* return;
* }
*/
// 当 head = dummy (new ListNode(-1)) 时,就不需要上面的边界条件处理了
ListNode p = head;
while (p.next != null) {
p = p.next;
}
p.next = node;
}
```

### 链表中常用的技巧-快慢指针

#### 找到链表的中点、中点前一个、中点后一个

这个是硬编码能力,需要大量练习打好基本功。

```java
/**
* 奇数的中点 & 偶数的中点靠前一位
*/
Node s = head;
Node f = head;
while (f.next != null && f.next.next != null) {
s = s.next;
f = f.next.next;
}

/**
* 奇数的中点 & 偶数的中点靠后一位
*/
while (f != null && f.next != null) {
s = s.next;
f = f.next.next;
}
```

如果要进一步继续偏移,修改 f 或 s 的初始节点即可。

注意:因为 f 一次走两步,所以:

- 想要获取中点往前的节点,修改 f 初始节点时 f=head.next.next,两个 next 才会让结果往前偏移一步
- 想要获取中点往后的节点,修改 s 的初始节点 s=head.next,一个 next 就可以让结果往后偏移一步

### 练习

- 反转链表(recursion&iteration 两种方式)(lc.206)

```java
// 迭代法,那就是双指针了,先存储后继节点,剩下的都好办
public ListNode reverseList(ListNode head) {
ListNode p = head, pre = null;
while(p != null) {
ListNode next = p.next; // 储存后继节点
p.next = pre;
pre = p;
p = next;
}
return pre;
}
// 递归法
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode newHead = reverseList(head.next); // newHead 是最后一个节点
// 后序遍历,假设递归到最后了,此时的 head 是最后一个节点的前一个节点
head.next.next = head; // 把下一个节点的next指向自身
head.next = null; // 把自身的next指向null
return newHead; // 返回尾部节点的引用指针即可
}
```

- 判断一个链表是否是回文结构(lc.234)

```java
/**
* 这道题解法很多,如果借助一个额外的栈就很简单,
* 但是面试中链表一定要找到额外空间复杂度最佳的方法。
* 要求时间复杂度 O(n), 空间复杂度 O(1)
*/
class Solution {
public boolean isPalindrome(ListNode head) {
// 要想空间复杂度为 O(1),把后半部分链表反转过来,然后比较即可
ListNode s = head, f = head;
while(f != null && f.next != null) {
s = s.next;
f = f.next.next;
}
// 此时 s 在中点或者中点的下一个节点,对后半部分反转链表(这里是另一种双指针反转方式)
ListNode curr = s;
while(s.next != null) {
// 缓存下下个节点,最后反转完了后,head恰好指向原来的尾 null 上
ListNode next = s.next.next;
s.next.next = curr; // 下一个节点指向当前节点
curr = s.next; // 当前节点指针移动到下个节点
s.next = next; // s节点指针指向下下个节点
}

boolean isPalindrome = true;
s = head;
while(curr != null && s != null) {
if(curr.val != s.val) {
return false;
}
curr = curr.next;
s = s.next;
}
/**
* 最后应该把反转的链表再反转回去[旺柴]
*/
return true;
}
}
```

- 将链表划分成左边小、中间相等、右边大的形式
```java
/**
* 这题如果借助数组,那不就是快排的 partition 吗?
*
* 要想时间复杂度为 O(1),那么就得借助 6 个变量 <head <tail =head =tail >head >tail
* 需要注意的点就是,要判断变量是否为 null
*/
```
- 复制含有随机指针的链表 (lc.138)

```java
/**
* 这道题要是空间复杂度不需要 O(1),那么用哈希表就挺好做的
*
* O(1) 的空间复杂度,就需要一定的技巧了,就是 拼接+拆分。
*/
// 哈希表
class Solution {
public Node copyRandomList(Node head) {
Map<Node, Node> map = new HashMap<>();
Node p = head;
while (p != null) {
map.put(p, new Node(p.val));
p = p.next;
}
p = head;
while(p != null) {
map.get(p).next = map.get(p.next);
map.get(p).random = map.get(p.random);
p = p.next;
}
return map.get(head);
}
}
```

```java
public Node copyRandomList(Node head) {
// 恰到好处的拼接,复制节点直接拼到原节点后,这样会发现:
// ** 新节点的random指向的就是原节点random指向节点的下一个节点 **
// 1.复制节点
Node p = head;
while (p != null) {
Node newNode = new Node(p.val);
newNode.next = p.next;
p.next = newNode;
p = newNode.next;
}
// 2.设置random
p = head;
while (p != null) {
if(p.random != null) {
p.next.random = p.random.next;
}
p = p.next.next;
}
// 3.分离链表
Node dummy = new Node(-1);
p = head;
Node curr = dummy;
while (p != null) {
curr.next = p.next;
curr = curr.next;
p.next = curr.next;
p = p.next;
}
return dummy.next;
}
```

- 两个单链表相交系列问题
- 1
- 2
- 3

0 comments on commit 6cc7766

Please sign in to comment.