数据结构: 链表问题总结

数据结构: 链表问题总结

刷了几个链表的题目,做一下总结.

题目来自 <程序员代码面试指南>

链表本身操作灵活,代码量少.很适合作为面试题考查.

链表的基本操作,如增删改查.

本文用到链表用的是牛客网上的结构.如下所示:

struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :val(x), next(NULL) {
	}
};

链表的题目中遇到的:

  • 快慢指针

    用来找节点,比如中间的节点.倒数第几个节点.

    判断链表是否有环,找环的入口.

  • 头结点(哨兵节点) :

    拼接链表时可以使用.比如将合并两个有序链表.

  • 翻转链表的操作

    翻转链表,注意保存 next 指针域. 且一个节点一个节点做.

  • 尾插法头插法

    用于生成目标的链表. 将划分的链表接起来. 注意:拼成的链表末尾指向NULL.

  • 还有其他一些待补充.

题目: 打印两个链表的公共部分

/*
	题目:
	打印两个有序单链表的公共部分.
	公共部分指的是相等的值
*/
vector<int> printCommonPart(ListNode* head1, ListNode* head2) {
	vector<int> res;
	if (head1 == NULL || head2 == NULL)
		return res;
	ListNode* p1 = head1;
	ListNode* p2 = head2;

	while (p1 != NULL&&p2 != NULL) {

		if (p1->val == p2->val) {
			res.push_back(p1->val);
			p1 = p1->next;
			p2 = p2->next;
		}
		else if (p1->val < p2->val) {
			p1 = p1->next;
		}
		else if (p1->val > p2->val) {
			p2 = p2->next;
		}
	}
	return res;
}

题目: 在单链表和双链表中删除倒数第K个节点.

  • 通过计数,找到倒数第K个节点的前驱
  • 删除操作,单链表和双链表有区别
// 在单链表中删除节点,需要知道前驱
/*
	有三种情况: 链表长度N,倒数第K个
	1) N<K.则返回空,
	2) N==K,则需要返回第一个节点
	3) N>K,需要两次遍历,1) 遍历完,每次k--
	   再一次从头遍历,每次k++,知道k==0.即找到顺数N-K个节点
*/
ListNode* RemoveLastKthNode(ListNode* &head, int lastKth) {
	if (head == NULL)
		return NULL;

	int k = lastKth;

	ListNode* cur = head;

	// 第一遍遍历
	while (cur != NULL) {
		cur = cur->next;
		k--;
	}

	if (k == 0) {
		return head->next;
	}
	else if (k > 0) {
		return NULL;
	}
	else { // k<0
		cur = head;
		// 使用++k找到的是倒数Kth的前驱.
		// 若使用k++,则找到的是倒数Kth节点
		while (++k != 0) {
			cur = cur->next;
		}
		ListNode* tobeDelete = cur->next;
		cur->next = tobeDelete->next;
		delete tobeDelete;
		tobeDelete = NULL;
		return head;
	}
}
DoubleLinkNode* RemoveLastKthNode(DoubleLinkNode* head, int lastKth) {

	if (head==NULL) {
		return NULL;
	}

	DoubleLinkNode* cur = head;
	int k = lastKth;

	while (cur != NULL) {
		cur = cur->next;
		k--;
	}

	if (k == 0) {
		return head->next;
	}
	else if (k > 0) {
		return NULL;
	}
	else {
		cur = head;
		// 使用++k找到的是倒数Kth的前驱.
		// 若使用k++,则找到的是倒数Kth节点
		while (++k != 0) {
			cur = cur->next;
		}
		// 找到了前驱
		DoubleLinkNode* tobedeleted = cur->next;
		DoubleLinkNode* next = tobedeleted->next;
		cur->next = next;
		next->last = cur;

		delete tobedeleted;
		tobedeleted = NULL;
		return head;
	}
}

题目: 翻转单链表中的一部分

  • 找到 m的前驱,找到n的后继.
  • 翻转 [m…n] 这个区间的链表
  • 注意边界情况.
ListNode* reverseBetween(ListNode* head, int m, int n) {

    if (head == NULL || n <= m) {
        return head;
    }

    int len = 0;
    ListNode* cur = head;
    ListNode* beforeM = NULL;
    ListNode* M = NULL;
    ListNode* N = NULL;
    ListNode* afterN = NULL;
    while (cur != NULL) {
        len++;
        beforeM = len == m - 1 ? cur : beforeM;
        M = len == m ? cur : M;
        N = len == n ? cur : N;
        afterN = len == n + 1 ? cur : afterN;
        cur = cur->next;
    }

    ListNode* res = head; // 返回的节点
    // reverse
    cur = M;
    ListNode* pre = beforeM;
    while (cur != afterN&&cur != NULL) {
        ListNode* next = cur->next;
        cur->next = pre;
        pre = cur;
        cur = next;
    }
    M->next = afterN;

    if (beforeM == NULL) {
        res = N;
    } else {
        beforeM->next = N;
    }

    return res;
}

题目: 将单链表按某值划分成为左边小中间相等右边大的形式

方法1: 将单链表放进去数组中,进行 类似于快排的 partition 操作,最后呢,将排序后的节点连接起来.

/*
	方法一:
	1. 链表的值放进数组中,
	2. 对数组进行partition
	3. 将数组中的链表重接

	时间复杂度O(N)
	空间复杂度O(N)
	且不能保证数据保持和输入链表一样的顺序
*/

void ArrPartition(vector<ListNode*> &v, int pivot) {
	int size = v.size();
	if (size == 0) {
		return;
	}

	int small = -1;
	int big = v.size();

	int index = 0;
	while (index < v.size()) {

		if (v[index]->val < pivot) {
			v[++small] = v[index++];
		}
		else if (v[index]->val > pivot) {
			swap(v[--big], v[index]);
		}
		else {
			// 当前这个值和pivot相等
			index++;
		}
	}
}

ListNode* LinkListPartition1(ListNode* head,int pivot) {
	if (head == NULL)
		return NULL;

	vector<ListNode*> v;

	// 把链表放进数组
	ListNode* cur = head;
	while (cur != NULL) {
		v.push_back(cur);
		cur = cur->next;
	}
		
	// 对数组进行partition
	cur = head;
	ArrPartition(v, pivot);

	for (int i = 1; i < v.size(); i++) {
		v[i - 1]->next = v[i];
	}
	v[v.size() - 1]->next = NULL;
	return v[0];
}

方法2: 创建三个链表,分别是 小于,等于,大于最后将三个链表进行连接

/*
	方法2:
	使用类似建立链表的方法,
	创建三个链表,分别是 小于,等于,大于
	最后将三个链表进行连接

	时间复杂度O(N)
	空间复杂度O(1)
	能保持顺序性
*/
ListNode* LinkListPartition2(ListNode* head, int pivot) {
	if (head == NULL)
		return NULL;

	ListNode* smallStart = NULL;
	ListNode* smallEnd = NULL;
	ListNode* equalStart = NULL;
	ListNode* equalEnd = NULL;
	ListNode* bigStart = NULL;
	ListNode* bigEnd = NULL;

	ListNode* cur = head;
	ListNode* next = NULL;
	while (cur!=NULL) {
		next = cur->next;
		cur->next = NULL; // 将每个节点都断开.就会保证每个单独的链表最后是指向空的
		// 尾插法
		if (cur->val < pivot) {
			if (smallStart == NULL) {
				smallStart = cur;
				smallEnd = cur;
			}
			else {
				smallEnd->next = cur;
				smallEnd = cur;
			}

		}
		else if (cur->val == pivot) {
			if (equalStart == NULL) {
				equalStart = cur;
				equalEnd = cur;
			}
			else {
				equalEnd->next = cur;
				equalEnd = cur;
			}
		}
		else {
			if (bigStart == NULL) {
				bigStart = cur;
				bigEnd = cur;
			} else {
				bigEnd->next = cur;
				bigEnd = cur;
			}
		}
		cur = next;
	}
	
	// 把小于和等于连接
	if (smallStart != NULL) {
		smallEnd->next = equalStart;
		// 如果等于部分为空,则将等于部分的
		equalEnd = equalEnd == NULL ? smallEnd : equalEnd;
	}
	// 将全部连接
	if (equalEnd != NULL) {
		equalEnd->next = bigStart;
	}

	return smallStart != NULL ? smallStart : equalStart != NULL ? equalStart : bigStart;

	
}

题目: 将搜索二叉树转换成为双向链表

方法1: 利用栈进行中序排序,最后将结果存储是队列中,再遍历队列,进行拼接.


/*
	将搜索二叉树转换为双向链表
*/
// 利用栈实现中序遍历,存储在队列中,逐一节点进行连接.注意头结点和末尾节点特殊处理
TreeNode* convert1(TreeNode* head) {

	TreeNode* res = NULL;
	TreeNode* end = NULL;

	queue<TreeNode*> q;
	TreeNode* cur = head;

	stack<TreeNode*> s;// 利用一个栈实现中序遍历

	while (!s.empty() || !cur != NULL) {
		while (cur != NULL) {
			s.push(cur);
			cur = cur->left;
		}

		if (!s.empty()) {
			cur = s.top();
			s.pop();
			q.push(cur);
			cur = cur->right;
		}
	}

	// 中序遍历的结果都存在队列中了
	while (!q.empty()) {

		cur = q.front();
		q.pop();

		if (res == NULL) {
			res = cur;
			res->left = NULL;
			end = cur;
		}
		else {
			cur->left = end;
			end->right = cur;
			end = cur;
		}
	}
	if(end!=NULL)
		end->right = NULL;

	return res;
}

方法2:利用递归函数.

/*
	利用递归函数,
	将整个二叉进行转换,
	返回都是双向链表的尾节点,且,尾节点的right指向头结点.
*/
TreeNode* convertProcess(TreeNode* head) {
	if (head == NULL) {
		return NULL;
	}

	TreeNode* leftTreeLast = convertProcess(head->left);
	TreeNode* rightTreeLast = convertProcess(head->right);

	TreeNode* leftTreeFirst = leftTreeLast != NULL ? leftTreeLast->right : NULL;
	TreeNode* rightTreeFirst = rightTreeLast != NULL ? rightTreeLast->right : NULL;
	
	if (leftTreeLast != NULL&&rightTreeLast != NULL) {
		leftTreeLast->right = head;
		head->left = leftTreeLast;
		head->right = rightTreeFirst;
		rightTreeFirst->left = head;
		rightTreeLast->right = leftTreeFirst;
		return rightTreeLast;
	}
	else if(leftTreeLast!=NULL){
		leftTreeLast->right = head;
		head->right = leftTreeFirst;
		head->left = leftTreeLast;
		return head;
	}
	else if (rightTreeLast != NULL) {
		head->right = rightTreeFirst;
		rightTreeFirst->left = head;
		rightTreeLast->right = head;
		return rightTreeLast;
	}
	else {
		head->right = head;
		return head;
	}
}

TreeNode* convert2(TreeNode* bst) {

	if (bst == NULL) {
		return NULL;
	}

	TreeNode* last = convertProcess(bst);
	// 最后把尾节点之指向头结点的指针取消掉了
	TreeNode* first = last->right;
	last->right = NULL;
	return first;
}

题目: 在O(1)时间删除链表节点

/*
	只给一个单链表的节点Node,让删除这个节点
*/
void DeleteNode(ListNode* node) {
	if (node == NULL) {
		return ;
	}

	ListNode* next = node->next;
	if (next == NULL) {
		throw "ERROR";
	}

	node->val = next->val;
	node->next = next->next;
	return ;
}

参考文献

链表问题总结1

链表问题总结2

猜你喜欢

转载自blog.csdn.net/qjh5606/article/details/85557674
今日推荐