题目来自LeetBook 系列:《链表》——https://leetcode-cn.com/leetbook/read/linked-list/x6ybqh/
文章目录
单链表
No.707 设计链表
https://leetcode-cn.com/problems/design-linked-list/
第一次写很多细节需要注意。头部和尾部添加别单独写,通过add调用更简单。注意add对于index的判断区间可以有size,这是大问题。
class MyLinkedList {
int size;
ListNode head;
//构造器
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
//获取链表中第 index 个节点的值。如果索引无效,则返回-1。
public int get(int index) {
if(index<0 || index>=size){
return -1;
}
ListNode curr = head;
for(int i=0; i<=index; i++){
curr = curr.next;
}
return curr.val;
}
//指定位置增加
public void addAtIndex(int index, int val) {
if(index > size) return;
if(index < 0) index = 0;
size++;
ListNode pred = head;
for(int i=0; i<index; i++) pred = pred.next;
ListNode addone = new ListNode(val);
addone.next = pred.next;
pred.next = addone;
}
//将值为 val 的节点追加到链表的第一个元素。
public void addAtHead(int val){
addAtIndex(0, val);
}
//将值为 val 的节点追加到链表的最后一个元素。
public void addAtTail(int val) {
addAtIndex(size, val);
}
//删除指定位置
public void deleteAtIndex(int index) {
if(index<0 || index>=size) return;
size--;
ListNode pred = head;
for(int i=0; i<index; i++) pred = pred.next;
pred.next = pred.next.next;
}
}
下面是懒得改对的第一遍版本
class MyLinkedList {
int size;//链表大小
ListNode head; //链表头节点
public MyLinkedList() {//构造器
size = 0;
head = new ListNode(0);
}
/**
* get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
*/
public int get(int index) {
if(index <0 || index >= size || size == 0){
return -1;
}
ListNode curr = head;
for(int i=0; i<=index; i++){
curr = curr.next;
}
return curr.val;
}
/**
* addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。
* 插入后,新节点将成为链表的第一个节点。
*/
public void addAtHead(int val) {
ListNode newone = new ListNode(val);
if(size == 0){
head.next = newone;
}else{
newone.next = head.next;
head.next = newone;
}
size++;
}
/**
* addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
* @param val
*/
public void addAtTail(int val) {
ListNode curr = head;
ListNode newtail = new ListNode(val);
if(size == 0){
head.next = newtail;
}else{
for(int i=0; i<size; i++){
curr = curr.next;
//System.out.println("第" + i + "个数为" + curr.val);
}
//System.out.println("已指向最后一个");
curr.next = newtail;
}
size++;
}
/**
* addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。
* 如果 index 等于链表的长度,则该节点将附加到链表的末尾。
* 如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
*/
public void addAtIndex(int index, int val) {
//System.out.println("当前i:");
if(index > size-1){//如果 index 大于链表长度,则不会插入节点
return;
}else if(index <= 0){//小于等于0就在头插
addAtHead(val);
}else{
System.out.println("当前i:");
ListNode curr = head;
ListNode newone = new ListNode(val);
for(int i=0; i<index; i++){
curr = curr.next;
System.out.println("当前i:" + i);
if(i == index-1){
ListNode nextone = curr.next;
curr.next = newone;
newone.next = nextone;
size++;
break;
}
}
}
}
/**
* deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
* @param index
*/
public void deleteAtIndex(int index) {
if(index < 0 || index >= size){
return;
}else if(index == 0){
ListNode oldone = head.next;
head.next = oldone.next;
size--;
}else{
ListNode curr = head;
for(int i=0; i<=index; i++){
curr = curr.next;
if(i == index-1){
ListNode nextone = curr.next;
ListNode nexttwo = nextone.next;
curr.next = nexttwo;
size--;
break;
}
}
}
}
}
双指针技巧
No.141 环形链表
https://leetcode-cn.com/problems/linked-list-cycle/
快慢指针,慢的被套圈就是有环。注意while终止条件!!
public boolean hasCycle2(ListNode head) {
if (head == null)
return false;
//快慢两个指针
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
//慢指针每次走一步
slow = slow.next;
//快指针每次走两步
fast = fast.next.next;
//如果相遇,说明有环,直接返回true
if (slow == fast)
return true;
}
//否则就是没环
return false;
}
No.142 环形链表2
https://leetcode-cn.com/problems/linked-list-cycle-ii/
双指针非常的巧妙啊: a=c+(n-1)(b+c)a=c+(n−1)(b+c)
public ListNode detectCycle(ListNode head) {
if (head == null)
return null;
//快慢两个指针
ListNode slow = head;
ListNode fast = head;
ListNode ans = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast){
while (ans != slow){
ans = ans.next;
slow = slow.next;
}
return ans;
}
}
//否则就是没环
return null;
}
No.160 相交链表
https://leetcode-cn.com/problems/intersection-of-two-linked-lists/
三元运算符真的很炫。
//简洁且美丽
public ListNode getIntersectionNode1(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
//错误且繁琐!!
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pa = headA;
ListNode pb = headB;
while(pa == pb || (pa == null && pb == null)){
if(pa.next != null){
pa = pa.next;
}else{
pa = headB;
}
if(pb.next != null){
pb = pb.next;
}else{
pa = headA;
}
}
if(pa == pb){
return pa;
}
if(pa == null && pb == null){
return null;
}
}
No.19 删除链表倒数第N个结点
- 整个遍历一遍求总数,然后正着删
- 双指针
//遍历一遍求总数正着删
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0,head);
int length = getLength(head);
ListNode cur = head;
for(int i = 1; i < length - n + 1; ++i) {
cur = cur.next;
}
cur.next = cur.next.next;
ListNode ans = dummy.next;
return ans;
}
public int getLength(ListNode head) {
int length = 0;
while (head != null) {
++length;
head = head.next;
}
return length;
}
//注意最后返回的是头指针的.next,而不是head,因为head可能被删除了
public ListNode removeNthFromEnd3(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode first = head;
ListNode second = dummy;
for (int i = 0; i < n; ++i) {
first = first.next;
}
while (first != null) {
first = first.next;
second = second.next;
}
second.next = second.next.next;
ListNode ans = dummy.next;
return ans;
}
经典问题
No.206 反转链表
- 新建一个链表头,挨个放到头前面
- 一个pre指前面,一个cur指现在,翻一下(还需要一个tmp存cur的下一个)
//新建一个链表头,挨个放到头前面
public ListNode reverseList1(ListNode head){
ListNode ans = null;
for(ListNode x = head; x!=null; x=x.next ){
ans = new ListNode(x.val,ans);
}
return ans;
}
//一个pre指前面,一个cur指现在,翻一下(还需要一个tmp存cur的下一个)
public ListNode reverseList2(ListNode head){
ListNode pre = null;
ListNode cur = head;
while(cur != null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
//错解,会丢失的,而且一直在1
public ListNode reverseList(ListNode head) {
ListNode dummy = new ListNode(0, head);
ListNode curr = head;
//dummy.next第一个结点
//curr.next该去头的结点
while(curr != null){
System.out.println(curr.val);
dummy.next = curr.next;
curr.next = curr.next.next;
curr.next.next = curr;
}
return dummy.next;
}
No.203 移除链表元素
https://leetcode-cn.com/problems/remove-linked-list-elements/
一遍过
public ListNode removeElements(ListNode head, int val) {
ListNode dummy = new ListNode(0, head);
ListNode pre = dummy;
while(pre != null){
//System.out.println("pre.val=" + pre.val);
while(pre.next != null && pre.next.val == val){
pre.next = pre.next.next;
}
pre = pre.next;
}
return dummy.next;
}
No.328 奇偶链表
一遍过,前面加上空链表判断更好。注意链表的结点后面是拖着东西的。
public ListNode oddEvenList(ListNode head) {
ListNode ji = new ListNode(0);
ListNode ou = new ListNode(0);
ListNode ji2 = ji;
ListNode ou2 = ou;
ListNode curr = head;
int a = 1;
while(curr != null){
if(a%2 == 1){//奇数
ListNode nowji = new ListNode(curr.val);
ji.next = nowji;
ji = ji.next;
}else if(a%2 == 0){//偶数
//System.out.println("偶,a=" + a + ", curr.val = " + curr.val);
ListNode nowou = new ListNode(curr.val);
ou.next = nowou;
ou = ou.next;
}
a++;
curr = curr.next;
}
ji.next = ou2.next;
return ji2.next;
}
双链表
No.707 设计链表
https://leetcode-cn.com/problems/design-linked-list/
先判断从头开始找还是从尾开始找更近。
public class ListNode {
int val;
ListNode next;
ListNode prev;
ListNode(int x) { val = x; }
}
class MyLinkedList {
int size;
// sentinel nodes as pseudo-head and pseudo-tail
ListNode head, tail;
public MyLinkedList() {
size = 0;
head = new ListNode(0);
tail = new ListNode(0);
head.next = tail;
tail.prev = head;
}
/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
public int get(int index) {
// if index is invalid
if (index < 0 || index >= size) return -1;
// choose the fastest way: to move from the head
// or to move from the tail
ListNode curr = head;
if (index + 1 < size - index)
for(int i = 0; i < index + 1; ++i) curr = curr.next;
else {
curr = tail;
for(int i = 0; i < size - index; ++i) curr = curr.prev;
}
return curr.val;
}
/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
public void addAtHead(int val) {
ListNode pred = head, succ = head.next;
++size;
ListNode toAdd = new ListNode(val);
toAdd.prev = pred;
toAdd.next = succ;
pred.next = toAdd;
succ.prev = toAdd;
}
/** Append a node of value val to the last element of the linked list. */
public void addAtTail(int val) {
ListNode succ = tail, pred = tail.prev;
++size;
ListNode toAdd = new ListNode(val);
toAdd.prev = pred;
toAdd.next = succ;
pred.next = toAdd;
succ.prev = toAdd;
}
/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
public void addAtIndex(int index, int val) {
// If index is greater than the length,
// the node will not be inserted.
if (index > size) return;
// [so weird] If index is negative,
// the node will be inserted at the head of the list.
if (index < 0) index = 0;
// find predecessor and successor of the node to be added
ListNode pred, succ;
if (index < size - index) {
pred = head;
for(int i = 0; i < index; ++i) pred = pred.next;
succ = pred.next;
}
else {
succ = tail;
for (int i = 0; i < size - index; ++i) succ = succ.prev;
pred = succ.prev;
}
// insertion itself
++size;
ListNode toAdd = new ListNode(val);
toAdd.prev = pred;
toAdd.next = succ;
pred.next = toAdd;
succ.prev = toAdd;
}
/** Delete the index-th node in the linked list, if the index is valid. */
public void deleteAtIndex(int index) {
// if the index is invalid, do nothing
if (index < 0 || index >= size) return;
// find predecessor and successor of the node to be deleted
ListNode pred, succ;
if (index < size - index) {
pred = head;
for(int i = 0; i < index; ++i) pred = pred.next;
succ = pred.next.next;
}
else {
succ = tail;
for (int i = 0; i < size - index - 1; ++i) succ = succ.prev;
pred = succ.prev.prev;
}
// delete pred.next
--size;
pred.next = succ;
succ.prev = pred;
}
}
小结
No.21 合并两个有序链表
https://leetcode-cn.com/problems/merge-two-sorted-lists/
一遍过
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if(list1 == null) return list2;
if(list2 == null) return list1;
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
ListNode c1 = list1;
ListNode c2 = list2;
while(c1 != null && c2 != null){
if(c1.val >= c2.val){
//System.out.println("c1.val=" + c1.val + ",c2.val=" + c2.val + ",1比2大");
ListNode newadd = new ListNode(c2.val);
cur.next = newadd;
cur = cur.next;
c2 = c2.next;
}else if(c1.val < c2.val){
//System.out.println("c1.val=" + c1.val + ",c2.val=" + c2.val + ",2比1大");
ListNode newadd = new ListNode(c1.val);
cur.next = newadd;
cur = cur.next;
c1 = c1.next;
}
//show(dummy.next);
}
if(c1 == null){
cur.next = c2;
}
if(c2 == null){
cur.next = c1;
}
return dummy.next;
}
No.2 两数相加
https://leetcode-cn.com/problems/add-two-numbers/
思路容易有。
易错点:
- 不是开头是0就返回另一个
- 有null出现也要再算一次,因为可能有进位
- 最高位单独判断要不要进位
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//不要开头是0就返回另一个
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
int iften = 0;
//有null出现后还要算一次,防止+1出现!
while(l1 != null || l2 != null ){
int n1 = l1==null? 0 : l1.val;
int n2 = l2==null? 0 : l2.val;
int ansnow = n1 + n2 + iften;
System.out.println("n1=" + n1 + ",n2=" + n2 + ",和为:" + ansnow);
if(ansnow >= 10){
iften = 1;
ansnow = ansnow - 10;
}else if(ansnow < 10){
iften = 0;
}
if(l1 != null) l1 = l1.next;
if(l2 != null) l2 = l2.next;
ListNode nodenow = new ListNode(ansnow);
curr.next = nodenow;
curr = curr.next;
}
if(iften == 1) curr.next = new ListNode(1);
return dummy.next;
}
No.430 扁平化多级双向链表(未完成)
https://leetcode-cn.com/problems/flatten-a-multilevel-doubly-linked-list/
设计递归算法
No.138 复制带随机指针的链表(未完成)
https://leetcode-cn.com/problems/copy-list-with-random-pointer/
起码用到哈希表知识
No.61 旋转链表
https://leetcode-cn.com/problems/rotate-list/
注意:
- 取模判断多余部分
- 头尾相接之前,先取模判断一下需不需要首尾相接
public ListNode rotateRight(ListNode head, int k) {
if (k == 0 || head == null || head.next == null) {
return head;
}
ListNode curr = head;
int n = 1;
while(curr.next != null){
curr = curr.next;
n++;
}
int add = n - k % n;//链表长度为n
if (add == n ) {
return head;
}
curr.next = head;//放到判断之后!!
curr = head;
for(int i=1; i<add; i++){
System.out.println(curr.val);
curr = curr.next;
}
ListNode ans = curr.next;
curr.next = null;
//起点是倒数k-1个,末尾是倒数k个
return ans;
}