链表-基础算法题

0.基本概念

  • 单链表结点组成
struct ListNode {
	int data;
	struct ListNode* next;
	
	ListNode() {}
	ListNode(int data, ListNode* next = NULL) : data(data), next(next) {}
};
  • 有无头结点只对尾插法的操作有影响,对头插法没有影响
  • 尾插法一定要提供尾指针
  • 在单链表的基础上可以派生出单循环链表、双链表、双循环链表

1.添加/删除链表节点

添加值为 x 的结点

带头结点的递增有序的单链表 L 添加一个值为 x 的结点,使其仍保持递增有序

void insert(ListNode* head, int x) {
    ListNode* pre = L;
    ListNode* cur = head->next;
    // 先找到要插入的位置
    while(cur != NULL) {
        if (x <= cur->data) {
           	break;                    
		} else {
           	pre = cur;
           	cur = cur->next;
        }
    }
    ListNode* s = new ListNode(x);
    pre->next = s;
    s->next = cur;
}

删除所有值为 x 的结点

迭代法:删除带头结点的单链表 L 中所有值为 x 的结点

void deleteAllx(ListNode* head, int x) {
    ListNode* pre = L;
    ListNode* cur = head->next;
    ListNode* u;
    while(cur != NULL) {
        if (cur->data == x) {
            pre->next = cur->next;
            u = cur;
            cur = cur->next;
            delete u;
        } else {
            pre = cur;
            cur = cur->next;
        }
    }
}

递归法:删除无头结点的单链表 L 中所有值为 x 的结点

void deleteAllx(ListNode* head, int x) {
    if (L == NULL) 
        return;
    if (head->data == x) {
        ListNode* u = L;
        L = head->next;
        delete u;
        deleteAllx(L, x);
    } else {
        deleteAllx(head->next, x);        
    }
}

删除所有重复值的结点

  • 删除递增有序的带头结点的单链表 L 中所有重复的元素
void deleteRepeat(ListNode* head) {
    ListNode* pre = head->next;
    ListNode* cur = pre->next;
    ListNode* u;
    while(cur != NULL) {
        if(pre->data == cur->data) {
            pre->next = cur->next;
            u = cur;
            cur = cur->next;
            delete u;
        } else {
            pre = pre->next;
            cur = cur->next;
        }
    }
}

删除给定指针指向的结点

  • 给定带头结点的单链表的某一个结点的指针,在 O ( 1 ) O(1) O(1) 内删除这个结点
void deleteSelectedNode(ListNode* node) {
    // 如果此结点是尾结点,我们还是要从头遍历到尾结点的前继结点,再将尾结点删除
    if (node->next == NULL) {
        ListNode* p = head;
        while (p->next != node) {
            p = p->next;
		}
        // 找到尾结点的前继结点,把尾结点删除
        delete p->next;
        p->next = NULL;
    } else {
        // 不是尾结点,就将后继结点的值赋给该节点,并删除后继结点
        node->data = node->next->data;
        ListNode* u = node->next;
        node->next = u->next;
        delete u;
    }
}

2.链表集合问题

求交集 C = A ∩ B

A 和 B 为2个递增有序的带头结点的单链表,求A和B的交集,结果存放在链表C中

在新链表上操作,在C中添加在A、B中都存在的元素

void getIntersection(ListNode* A, ListNode* B, ListNode* C) {
    ListNode* pa = A->next; 
    ListNode* pb = B->next;
    ListNode* pc = C->next;
    while(pa != NULL && pb != NULL) {
        if (pa->data < pb->data) {
            pa = pa->next;
        } else if (pa->data > pb->data) {
            pb = pb->next;
        } else {
            ListNode* s = new ListNode(pa->data);
            pc->next = s;
            pc = s;            // 将 pc 指向最后一个结点 
            pa = pa->next;
            pb = pb->next;
        }
    }
    pc->next = NULL;    // 最后一定要置空,因为c++默认是野指针
}

求并集 A = A ∪ B

A 和 B 为2个递增有序的带头结点的单链表,求 A 和 B 的并集,结果存放在链表A中

在原链表上操作,在A中添加在B中存在,A中不存在的元素

void getUnion(ListNode* A, ListNode* B) {
    ListNode* pa = A->next; 
    ListNode* pb = B->next;
    ListNode* pre = A;
	ListNode* u;
    while(pa != NULL && pb != NULL) {
        if (pa->data < pb->data) {      	
			pre = pa;
			pa = pa->next;
        } else if(pa->data > pb->data) {  	
        	u = pb->next;	  // 暂存后继结点
        	pb->next = pa;			
        	pre->next = pb;
        	pb = u;
        } else {          
			pre = pa;               	
			pa = pa->next;
			pb = pb->next;
        }
    }
    // 将B中剩余结点直接拼接到A表尾部
    if (pb != NULL) {
		pre->next = pb;		
	}	
}

求交集 A = A ∩ B

A 和 B 为2个递增有序的带头结点的单链表,求 A 和 B 的交集,结果存放在链表A中

在原链表上操作,在A中删除A中存在,B中不存在的元素

void getIntersection(ListNode* A, ListNode* B) {
    ListNode* pa = A->next; 
    ListNode* pb = B->next;
    ListNode* pre = A;
    ListNode* u;
    while(pa != NULL && pb != NULL) {
        if (pa->data < pb->data) {      
            pre->next = pa->next;	// A当前的元素在B中不存在,删除
            u = pa;
            pa = pa->next;
            delete u;
        } else if(pa->data > pb->data) {    
            pb = pb->next;		// 不确定A当前的元素在B中是否存在,B要继续向后遍历
        } else {                         
            pre = pa;	        // 相等则保留,同时要把pre指针往后移
            pa = pa->next;
            pb = pb->next;
        }
    }
}

求差集 A = A - B

A和B为2个递增有序的带头结点的单链表,求 A 和 B 的差集,结果存放在链表A中

在原链表上操作,在A中删除A、B中都存在的元素:A - B = A - A ∩ B

void getSubtraction(ListNode* A, ListNode* B) {
    ListNode* pa = A->next; 
    ListNode* pb = B->next;
    ListNode* pre = A;
    ListNode* u;
    while(pa != NULL && pb != NULL) {
        if (pa->data < pb->data) {      	
            pre = pa;	            // A当前的元素在B中不存在则保留,同时把pre指针往后移
            pa = pa->next;
        } else if(pa->data > pb->data) {  	
            pb = pb->next;          // 不确定A当前的元素在B中是否存在,B要继续向后遍历
        } else {                         	
            pre->next = pa->next;	// A当前的元素在B中存在,删除
            u = pa;
            pa = pa->next;
            pb = pb->next;
            delete u;
        }
    }  
}

判断 A ⊆ B

A和B为2个递增有序的带头结点的单链表,判断A是否为B的子集

bool isSubset(ListNode* A, ListNode* B) {
    ListNode* pa = A->next;
    ListNode* pb = B->next;
    while(pa != NULL && pb != NULL) {
        if (pa->data < pb->data) {
            return false;   
        } else if(pa->data > pb->data) {
            pb = pb->next;
        } else {
            pa = pa->next;
            pb = pb->next;
        }
    }
    return true;
}

3.链表合并

递增合并为递增

A 和 B 为2个递增有序的带头结点的单链表,将两者合并为递增有序的单链表

ListNode* mergeAsc(ListNode* A, ListNode* B) {
	ListNode* head = new ListNode(0);
	ListNode* pre = head;
	ListNode* pa = A->next;
	ListNode* pb = B->next;
	while (pa != NULL && pb != NULL) {
		if (pa->data <= pb->data) {
			pre->next = pa;
			pa = pa->next;
		} else {
			pre->next = pb;
			pb = pb->next;
		}
		pre = pre->next;
	}
	pre->next = pa != NULL ? pa : pb;
	return head;
}

递增合并为递减

A和B为2个递增有序的带头结点的单链表,将两者合并为递减有序的单链表

ListNode* mergeDesc(ListNode* A, ListNode* B) {
	ListNode* head = NULL;
	ListNode* pa;
	ListNode* pb;
	while (A->next != NULL && B->next != NULL) {
		pa = A->next;
		pb = B->next;
		if (pa->data <= pb->data) {
			A->next = pa->next;
			pa->next = head;
			head = pa;
		}
		else {
			B->next = pb->next;
			pb->next = head;
			head = pb;
		}
	}
	while (A->next != NULL) {
		pa = A->next;
		A->next = pa->next;
		pa->next = head;
		head = pa;
	}
	while (B->next != NULL) {
		pb = B->next;
		B->next = pb->next;
		pb->next = head;
		head = pb;
	}
	return head;
}

4.链表翻转

给定链表 head→1→2→3→4,将其翻转为 head→4→3→2→1

头插法

void reverseList(ListNode* head) {
    ListNode* temp = null;   // 建立一个临时的无头结点的链表
    ListNode* cur;			 // 用于遍历原链表
    while(head->next != null) {
        cur = head->next; 
        head->next = cur->next;  // 当前结点从原链表中删除
        cur->next = temp;     // 用头插法将结点插入temp链表
		temp = cur;
    }
    head->next = temp;
}

迭代法

void reverseList(ListNode* head) {
    ListNode* pre = head->next;
    ListNode* cur = pre->next;
    ListNode* temp;
    while(cur != null) {
        // 在cur指向pre之前一定要先保留cur的后继结点,不然cur指向pre后就找不到后继结点了
        temp = cur->next;
        cur->next = pre;
        pre = cur;
        cur = temp;
    }
    head->next->next = null;  // 1.删除环并置空
    head->next = pre;         // 2.修改头结点后继
}

list-reverse1

就地翻转法

void reverseList(ListNode* head) {
    ListNode* pre = head->next;
    ListNode* cur = pre->next;
    while(cur != null) {
        pre->next = cur->next;
        cur-next = head->next;
        head->next = cur;
        cur = pre->next;
    }
}

list-reverse2

5.双循环链表

判断双循环链表是否对称

void isSymmetric(ListNode* head) {
    ListNode* p = head->next;
    ListNode* q = head->prior;
    while(p != q && q->next != p) {
		if (p->data == q->data) {
			p = p->next;
			q = q->prior;
		} else {
			return false;
		}
    }
    return true;
}

6.双指针

  • 给定一个带头结点的单链表,返回链表倒数第 k 个结点
  • 给定一个带头结点的单链表,返回链表最中间的结点
  • 判断一个链表是否有环
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值