package linkedList
import (
"fmt"
"testing"
)
/*
链表问题
面试时链表解题的方法论
1.对于笔试,不用太在乎空间复杂度,一切为了时间复杂度
2.对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法(吸引面试官要你不要别人)
链表面试题常用的数据结构
1.使用容器 哈希表、数组等
2.快慢指针
快慢指针
1.输入链表头节点,奇数长度返回中点,偶数长度返回上中点
2.输入链表头节点,奇数长度返回中点,偶数长度返回下中点
3.输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
4.输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
四种题边界条件不一样
*/
type Node struct {
Value int
Next *Node
}
func midOrUpMidNode(head *Node) *Node {
if head == nil || head.Next == nil || head.Next.Next == nil {
return head
}
slow, fast := head.Next, head.Next.Next
for fast.Next != nil && fast.Next.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
return slow
}
func midOrDownMidNode(head *Node) *Node {
if head == nil || head.Next == nil {
return head
}
slow, fast := head.Next, head.Next
for fast.Next != nil && fast.Next.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
return slow
}
func midOrUpMidPreNode(head *Node) *Node {
if head == nil || head.Next == nil || head.Next.Next == nil {
return head
}
slow, fast := head, head.Next.Next
for fast.Next != nil && fast.Next.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
return slow
}
func midOrDownMidPreNode(head *Node) *Node {
if head == nil || head.Next == nil {
return nil
}
if head.Next.Next == nil {
return head
}
slow, fast := head, head.Next
for fast.Next != nil && fast.Next.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
return slow
}
/*
常见面试题
给定一个单链表的头节点head,请判断该链表是否为回文结构
1.栈的方法特别简单(笔试用) 栈 全放进去 弹出比较 or 数组 从后往前遍历
2.改原链表的方法就需要注意边界了(面试用)
少一点的空间,用快慢指针,奇数的时候,定位到中点 偶数的时候定位到上中点 ,然后把上中点后边的数加入栈中
彻底不用容器
奇数个的时候,对中点后所有元素逆序 然后从前往后比对,返回答案之前,调节好指针指向,保证和最初一样
偶数个的时候,让上中点指向nil,对下中点后的元素逆序
*/
func isOakubdrine(head *Node) bool {
if head == nil || head.Next == nil {
return true
}
n1, n2 := head, head
for n2.Next != nil && n2.Next.Next != nil { // 找到中点
n1 = n1.Next
n2 = n2.Next.Next
}
n2 = n1.Next // n2 右侧第一个节点
n1.Next = nil
var n3 *Node
for n2 != nil {
n3 = n2.Next
n2.Next = n1
n1 = n2
n2 = n3
}
n3 = n1
n2 = head
res := true
for n1 != nil && n2 != nil {
if n1.Value != n2.Value {
res = false
break
}
n1 = n1.Next
n2 = n2.Next
}
n1 = n3.Next
n3.Next = nil
for n1 != nil {
n2 = n1.Next
n1.Next = n3
n3 = n1
n1 = n2
}
return res
}
func NewList(val ... int) *Node {
root := &Node{
Value: 0,
Next: nil,
}
cur := root
for k := range val {
node := &Node{
Value: val[k],
Next: nil,
}
cur.Next = node
cur = cur.Next
}
root = root.Next
return root
}
func TestList(t *testing.T) {
head := NewList(1,2,3,4,5,6,7,8,9)
fmt.Println(isOakubdrine(head))
fmt.Println(midOrUpMidNode(head).Value)
fmt.Println(midOrDownMidNode(head).Value)
fmt.Println(midOrUpMidPreNode(head).Value)
fmt.Println(midOrDownMidPreNode(head).Value)
}
/*
常见面试题
将单向链表按某只划分成左边小、中间相等、右边大的形式
1.把链表放入数组中,在数组上做partition (笔试用)
2.分成小中大三部分,再把各个部分之间串起来 (面试用)
*/
func listPartition(head *Node, pivot int) *Node {
var sH,sT, eH,eT,mH, mT *Node
for head != nil {
if head.Value < pivot {
if sH == nil {
sH = head
sT = head
}else {
sT.Next = head
sT = sT.Next
}
}else if head.Value == pivot {
if eH == nil {
eH = head
eT = head
}else {
eT.Next = head
eT = eT.Next
}
}else {
if mH == nil {
mH = head
mT = head
}else {
mT.Next = head
mT = mT.Next
}
}
head = head.Next
}
//小于区域的尾巴,连接等于区域的头,等于区域的尾巴连大于区域的头
if sT != nil {
sT.Next = nil
}
if eT != nil {
eT.Next = nil
}
if mT != nil {
mT.Next = nil
}
if sT != nil {
sT.Next = eH
if eT == nil {
eT = sT
}
}
if eT != nil {
eT.Next = mH
}
if sH != nil {
return sH
}
if eH != nil {
return eH
}
return mH
}
func TestListPartition(t *testing.T) {
head := NewList(1,2,2,33,33,4,5,5,6,8,6,88,99,6,7,8,9,189,26,69,65,6,7,5,6)
head = listPartition(head,189)
for head != nil {
fmt.Print(head.Value,"->")
head = head.Next
}
}
/*
常见面试题
一种特殊的单链表节点类型描述如下
type Node struct {
value int
Next *Node
Rand *Node
}
Rand指针是单链表节点结构中新增的指针,Rand可能指向链表中的任意一个节点,也可能指向nil
给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
要求:时间复杂度O(N), 额外空间复杂度O(1)
__________________________
| |
| '
0 ---------> 1 -------> 2 ---------> 3 -----> nil
| '
| |
-------------------------
next 指针无环
先遍历 建立对应关系
map[*Node]*Node key 老类型 value 新类型
再建立Rand的关系
*/
type node struct {
value int
next *node
rand *node
}
func copyListWithRand1(head *node) *node {
if head == nil {
return nil
}
dic := map[*node]*node{}
cur := head
for cur != nil {
dic[cur] = &node{
value: cur.value,
next: nil,
}
}
cur = head
for cur != nil {
dic[cur].next = dic[cur.next]
dic[cur].rand = dic[cur.rand]
cur = cur.next
}
return dic[head]
}
/*
__________________________
| |
| '
0 ---------> 1 -------> 2 ---------> 3 -----> nil
| '
| |
-------------------------
______________________________________________________
| |
| '
0 -----> 0' ----> 1 ---> 1' ----> 2 -----> 2' ----> 3 ---> 3' --> nil
| '
| |
-----------------
*/
func copyListWithRand2(head *node) *node {
if head == nil {
return nil
}
var cur, next *node
cur = head
for cur != nil {
next = cur.next
cur.next = &node{
value: cur.value,
next: nil,
rand: nil,
}
cur.next.next = next
cur = next
}
cur = head
var curCopy *node
for cur != nil {
next = cur.next.next
curCopy = cur.next
if cur.rand != nil {
curCopy.rand = cur.rand.next
}else {
curCopy.rand = nil
}
cur = next
}
res := head.next
cur = head
//分离
for cur != nil {
next = cur.next.next
curCopy = cur.next
cur.next = next
if next != nil {
curCopy.next = next.next
}else {
curCopy.next = nil
}
cur = next
}
return res
}
/*
该题和约瑟夫环问题称为链表问题的两个噩梦
常见面试题
给定两个可能有环也可能无环的单链表,头节点head1和head2
请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交,返回null
要求:
如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)
和值没关系,跟内存地址有关系
hashSet 就可以 存内存地址 先查有没有 没有放进去, 有 就是第一个相交节点
环内 6个点 环外8个点
快慢指针
先找到相遇节点 让F指向头,再次相遇就是第一个相交节点 小学奥数的追及问题
*/
//找到链表第一个入环节点,如果无环,返回nil
func getLoopNode(head *Node) *Node {
if head == nil || head.Next == nil || head.Next.Next == nil {
return nil
}
n1, n2 := head.Next, head.Next.Next
for n1 != n2 {
if n2.Next == nil || n2.Next.Next == nil {
return nil
}
n2 = n2.Next.Next
n1 = n1.Next
}
n2 = head
for n1 != n2 {
n1 = n1.Next
n2 = n2.Next
}
return n1
}
/*
head1 head2
loop1 loop2
两个无环链表相交,必用最后的公共部分,用hash表方式找第一个相交节点,可以
不用hash表
head1 ....... 走到最后一个节点 假设长度 100
head2 ....... 走到最后一个节点 假设长度 80
判断两个节点地址是否相同 不相同则不相交
长链表先走 100 -80 短链表和长链表一起走 一定相遇
1. loop1 loop2 都为nil
2. 一个为nil 另一个不为nil // 不可能相交
2. 一个为nil 另一个不为nil // 不可能相交
3. 都不为nil 3种情况 >都有环,不相交 、 >都有环,两个链表入环节点时一个、 >入环节点不是一个
如果两个有环节点相交 两个链表一定公用环
*/
//如果量链表都无环,返回第一个相交节点,如果不相交,返回nil
func noLoop(head1, head2 *Node) *Node {
if head1 == nil || head2 == nil {
return nil
}
cur1, cur2 := head1, head2
n := 0
for cur1.Next != nil {
n++
cur1 = cur1.Next
}
for cur2.Next != nil {
n--
cur2 = cur2.Next
}
if cur1 != cur2 {
return nil
}
// n : 链表1长度 减去 链表2 长度的值
cur1 = head2
if n > 0 {
cur1 = head1
}
cur2 = head1
if cur1 == head1 {
cur2 = head2
}
n = abs(n)
for n != 0 {
n--
cur1 = cur1.Next
}
for cur1 != cur2 {
cur1 = cur1.Next
cur2 = cur2.Next
}
return cur1
}
func abs(n int) int {
if n < 0 {
return -n
}
return n
}
//两个有环链表,返回第一个相交节点,如果不相交,返回nil
func bothLoop(head1, loop1, head2,loop2 *Node) *Node {
var cur1, cur2 *Node
if loop1 == loop2 {
cur1 = head1
cur2 = head2
n := 0
for cur1 != loop1 {
n++
cur1 = cur1.Next
}
for cur2 != loop2 {
n--
cur2 = cur2.Next
}
cur1 = head2
if n > 0 {
cur1 = head1
}
cur2 = head1
if cur1 == head1 {
cur2 = head2
}
n = abs(n)
for n != 0 {
n--
cur1 = cur1.Next
}
for cur1 != cur2 {
cur1 = cur1.Next
cur2 = cur2.Next
}
return cur1
}else {
cur1 = loop1.Next
for cur1 != loop1 {
if cur1 == loop2 {
return loop1
}
cur1 = cur1.Next
}
return nil
}
}
/*
这是一个综合题目
1.给一个链表,返回第一个入环节点
2.两个无环链表相交,返回第一个相交节点
3.两个有环链表相交,找到第一个相交节点
*/
func getIntersectNode(head1,head2 *Node) *Node {
if head1 == nil || head2 == nil {
return nil
}
loop1 := getLoopNode(head1)
loop2 := getLoopNode(head2)
if loop1 == nil && loop2 == nil {
return noLoop(head1,head2)
}
if loop1 != nil && loop2 != nil {
return bothLoop(head1,loop1,head2,loop2)
}
return nil
}
/*
常见面试题
能不能不给单链表的头节点,只给想要删除的节点,就能做到在链表上把这个点删除
有! 将下一个节点的值复制给自己 自己的next指针指向下一个节点的next位置 --- 》 借尸还魂
1.有问题,删掉的是下一个节点,只是替代了内容
2.如果非常复杂,可能无法copy
3.绝对无法删除链表最后一个节点
node1 := Node{}
node2 := Node{}
node3 := Node{}
node1.Next = node2
node2.Next = node3
node3 = nil 并未真正删除
*/
链表——题目集合
于 2021-10-25 22:26:06 首次发布