logo
Published on

基础算法与数据结构(一) 链表

字数 1357

本部分内容一般选用C++,如有Java代码,将有明确标注。

链表的基础数据结构

	struct ListNode{
		int val;          //may be the other data type
		ListNode* next;
		ListNode(int val){
			this.val = val;
			this.next = null;
		}
	}

链表的基本性质

  • 链表克服了数组增删改操作需要移动大量元素的缺点,它不要求逻辑上相邻的两个元素在物理实现上相邻(数组要求)。但失去了数组可以随机存取的优点。
  • 链表的存取必须从第一个结点开始。所以对链表进行增删改操作时的最坏时间复杂度应为O(n)
  • 静态链表:用数组模仿链表,数组的第二个数据存下一个结点的位置

链表的基础增删以及逆转操作

  • 新增元素:
	//pos is the number which count from the 0 and don't have the dommy node
	void addNodes(ListNode* head,int val,int pos){
		ListNode* cur = head;
		while(pos-- && cur){
			cur = cur->next;
		}
		if(cur){
			ListNode* next = cur->next;
			cur->next = new ListNode(val);
			cur->next->next = next;
		}
	}
  • 删除元素
	//pos is the number which count from the 0 and don't have the dommy node
	void deleteNodes(ListNode* head,int pos){
		ListNode* cur = head;
		int nowPos = j;
		while(cur && j<i-1){
			cur = cur->next;
			j++;
		}
		ListNode *del = cur->next;
		cur-next = del->next;
	}
  • 逆转操作
  // the head node is the node which is the fist node of the sublist which will be reversed.
  //no recursion
	ListNode* Reverse(ListNode* head){
		ListNode* pre = null;
		ListNode* next = null;
		while(head){
			next = head->next;
			head->next = next;
			pre = head;
			head = next;
		}
		return pre;
	}

  //recursion
	ListNode* Reverse(ListNode* head){
		if(!head || !head->next) return head;
		ListNode* thead = Reverse(head->next);
		head->next->next = head;
		head->next = NULL;
		return thead;
	}

链表题 — 快慢指针

基本思想为 快指针一次走两步,慢指针一次走一步,根据产生的效果判断一些问题。注意点即指针的有效性问题。

判断单链表是否为循环链表

  • 基本理念:如果存在循环,总有fast和slow相遇的时候,如果相遇则退出。如果是因为这个退出循环的,那么总存在fast或者fast的next节点不为空,即返回真值。如果是因为fast节点指向空才跳出的循环,那么就不存在循环。
	bool isExistLoop(ListNode* head){
		ListNode* fast,slow;
		fast = slow = head;
		while(fast && fast->next){
			slow = slow->next;
			fast = fast->next->next;
			if(slow == fast) break;
		}
		return (!fast || !fast->next)
	}

在有序列表中寻找中位数(在链表中找到中间)

	int findMid(ListNode* head){
		ListNode* slow,fast;
		slow = fast = head;
		while(fast->next && fast->next->next){
			slow = slow->next;
			fast = fast->next->next;
		}
		if(!fast->next) return slow->val;
		else return ((slow->val)+(slow->next->val))/2;
	}

有环链表寻找入口点

  • 原理: fast和slow的相遇点和起点分别设为两个指针,每次各走一步,则这两个指针的相遇点即为入口点。
  • 证明: 设 起点到环的入口的长度为a,入口处到快慢指针相遇处的长度为b 那么慢指针从开始到相遇处走了(a+b)步,那么快指针走了2(a+b)步 设快指针此时绕着环走了n圈,环的长度为r 那么有 2(a+b) = (a+b) + n _ r
    → a+b = nr → a = (n-1) _ r + r - b
    相遇点开始的走了n-1圈和r-b的长度回到起点的同时,起点开始的来到了环的起点。

代码略

双指针问题

基础思想: 慢指针从头开始,快指针从k开始,当快指针走到结束时,慢指针走到倒数第k个的位置。

例题: LEETCode 234 Palindrome Linked List _ 一般链表解法: 用双指针找到中点位置 → 中点的下一个结点开始到末尾逆转链表 → 从头尾开始向中间,如果出现不同的点则不为回文 _ 巧解: 转为字符串,并且reverse,如果为相同字符串那么为回文串

链表相交问题

首先明确,如果两个链表有相交,一定出现Y字形的相交,相交点之后的两个链表相同,不可能出现X型的相交。

分类讨论链表是否有环。

  • 两个无环链表 : 判断末尾是否相同,如果为同一个节点那么相交(判断地址) 求交点: 遍历两个链表得到两个链表的长度len1,len2,从长的链表先走|len2-len1|的长度,在让另一指针从短的头开始走,两个指针相等的那个结点即为交点

  • 可能有环 判断有无环
    _ 两个均无环: 划归为1 _ 一个有环一个无环 : 根据首先明确的,两个不可能相交 * 两个有环 : 从一个链表上得到快慢指针的第一个相交点,判断这个相交点是否在第二个链表上,如果在第二个链表的环上那么一定相交。然后划归为1(链表长度为链表起点到环的入口处)

链表题 — 归并有序链表

两个链表的归并

	ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
       ListNode* start = new ListNode(0);
       ListNode* pre = start;
       if(!l1 ||!l2 ) return l1?l1:l2;
       while(l1 != NULL && l2 != NULL){
           if(l1->val < l2->val){
               pre->next = l1;
               l1 = l1->next;
           }else{
               pre->next = l2;
               l2 = l2->next;
           }
           pre = pre->next;
       }
       pre->next = l1?l1:l2;
       return start->next;
    }

k个链表的归并

  • 两两归并 O(kn^2)
  • 通过优先队列每次取出最小值节点归并
  • 分治法归并

分治法归并代码:

	ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.empty()) return NULL;
        return divideAndConquer(lists,0,lists.size()-1);
    }

    ListNode* divideAndConquer(vector<ListNode*> &lists,int left,int right){
        if(left == right) return lists[right];
        int mid = (left+right)/2;
        ListNode* left_merge = divideAndConquer(lists,left,mid);
        ListNode* right_merge = divideAndConquer(lists,mid+1,right);
        return mergeTwoLists(left_merge,right_merge);
    }