链表

一、C++中的STL容器使用迭代器删除问题

在我们使用迭代器iterator遍历STL容器的时候,我们并不能直接在使用迭代器遍历的时候进行删除,否则就会报错,例如:

//在用迭代器遍历容器的时候进行删除操作
vecor<int> v;
vector<int>::iterator itr;
for(itr=v.begin();itr!=v.end();++itr)
{
     if(...)
     v.erase(itr);
}

上述代码的运行的时候就会报错,因为在删除的过程中其破坏了迭代器。

1、结点式容器

结点式迭代器:
结点式的容器有map,list,set,这些容器在插入和删除的时候会导致指向该元素的迭代器失效,其他元素的迭代器不受影响
所以这种容器的正确的删除方式为:

    set<int> s;
	s.insert(1);
	s.insert(2);
	s.insert(3);
	set<int>::iterator itr = s.begin();
	while (itr != s.end())
	{
		if (*itr == 2)
			s.erase(itr++);
		else
			itr++;
	}

这里在删除的时候,同时将迭代器向下指向下一个有效的地方,可以理解为erase语句结束之后被删除的iterator就失效了,但是将++写在同一个语句中,那么在失效之前会先执行++操作,所以得到的就是下一个有效的iterator。

2、顺序式容器

顺序式容器:
顺序式容器有vector,string,deque等,这些容器在插入和删除的时候会导致当前的迭代器以及其后的所有迭代器失效。
其正确的删除方式为:

	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	vector<int>::iterator itr = v.begin();
	while (itr != v.end())
	{
		if (*itr == 2)
			itr = v.erase(itr);
		else
			itr++;
	}

调用erase之后其会返回紧接在被删除或者插入元素之后的有效的迭代器,所以不必使用++,直接将其返回值赋给iterator就可以了。

3、在Java中如何处理该问题

在Java中如何处理该问题:
在Java中也会有类似的问题,也就是说在使用迭代器遍历的时候不能直接进行删除,下面使用map来表示正确的方法:

public class test
{
	List<String> removeKeys = new ArrayList<>();
	for (Entry<String, Integer> entry : map.entrySet())
	{
		String key = entry.getKey();
		Integer value = entry.getValue();
		if (value.equals(1))
		{
			removeKeys.add(key);
		}
	}
	for (String key : removeKeys)
	{
		map.remove(key);
	}
};

将要删除的数据先放在list中,然后再进行删除。

二、链表

链表问题中经常使用的技巧:

  • 使用额外的结构进行储存,例如哈希表等
  • 使用快慢指针

与链表相关的常见问题:

1、反转链表:反转单向链表和双向链表
//反转单链表
struct linkedlist
{
	int value;
	linkedlist * next;
};
linkedlist* reverse(linkedlist* head)
{
	linkedlist* pre = head;
	linkedlist* cur = head->next;
	linkedlist* next = NULL;
	head->next = NULL;
	while (cur != NULL)
	{
		next = cur->next;
		cur->next = pre;
		pre = cur;
		cur = next;
	}
	return pre;//此时的pre指向的是新链表的开始的节点
}

//反转双向链表
//emm,为什么要反转双向链表不太理解,只需要改变一下首节点和末尾结点就可以了啊

王一博是我每天学习的动力,哼,不愧是他!!!!
哼,不愧是我

2、打印两个有序链表的公众部分:

因为是有序的,所以两个链表从头开始进行遍历,比较两个结点的值,哪个链表的节点值小就移动哪个链表的指针,如果值相等那么打印,也就是谁小移动谁,相等就打印。

3、回文链表

判断一个链表是否为回文链表。
a、方法一:使用栈,遍历一遍链表,将结点压栈,然后再将栈中的元素与链表进行逐个比较,只要出现一个不一致,那么就不是回文链表。

struct linkedlist
{
	int value;
	linkedlist * next;
	linkedlist(int x) :value(x), next(NULL) {}
};
bool isPalindrome(linkedlist* head)
{
	stack<linkedlist*> s;
	linkedlist* tmp = head;
	while (tmp != NULL)
	{
		s.push(tmp);
		tmp = tmp->next;
	}
	while (head != NULL)
	{
		tmp = s.top();
		s.pop();
		if (tmp->value != head->value)
			return false;
		head = head->next;
	}
	return true;
}

b、方法二:使用快慢指针找到链表的中间结点,然后将链表的一半入栈,然后比较两半是否是相同的,相比较方法一,该方法少用了一半的空间复杂度。

struct linkedlist
{
	int value;
	linkedlist * next;
	linkedlist(int x) :value(x), next(NULL) {}
};
bool isPalindrome(linkedlist* head)
{
	stack<linkedlist*> s;
	linkedlist* tmp = head;
	linkedlist* fast = head;
	linkedlist* slow = head;
	//需要判断链表的数目是偶数还是奇数
	if (head->next == NULL)//只有一个结点
		return true;
	while (fast != NULL || fast->next != NULL)
	{
		slow = slow->next;
		fast = fast->next->next;
	}
	//slow指向的是中间的位置
	while (tmp != slow)
	{
		s.push(tmp);
		tmp = tmp->next;
	}
	while (head != slow)
	{
		tmp = s.top();
		s.pop();
		if (tmp->value != head->value)
			return false;
		head = head->next;
	}
	return true;
}

c、方法三:找到中间的节点之后,将后半部分的链表反转,判断两部分是否是相同的,如果相同,那么就会回文链表,否则不是。判断结束之后将反转的链表再恢复过来。这种方法相较前面两种方法虽然多了一些常数的时间开销,但是在空间复杂度方面达到了O(1)。

struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) {
        // write code here
        ListNode* pre = NULL;
        ListNode* cur = NULL;
        ListNode* fast = A;
        ListNode* slow = A;
        bool res = true;
        //只有一个结点或者没有结点的时候
        if(A->next==NULL||A==NULL)
            return true;
      //该条件的判断刚好可以保证
     //当节点的个数是奇数的时候,slow指向的就是中间结点
     //当结点的个数是偶数的时候,slow指向的就是中间两个结点的前一个结点
       while(fast->next->next!=NULL&&fast->next!=NULL)
       {
           fast = fast->next->next;
           slow = slow->next;
       }
        //此时slow指向的是中间的节点
        //进行反转链表
        pre = slow;
        cur = slow->next;
        slow->next = NULL;
        ListNode* next = NULL;
        while(cur!=NULL)
        {
            next = cur->next;
            cur->next = pre;
             pre = cur;
            cur = next;
        }
        //此时的pre指向的是反转后的链表的head
        cur = A;
        ListNode* head = pre;
       while(cur!=NULL)
       {
           if(pre->val!=cur->val)
           {
               res = false;
               break;
           }
           cur = cur->next;
           pre = pre->next;
       }
        //运行到这类说明是回文链表,然后将反转的链表恢复
        cur = head->next;
        pre = head;
        head->next = NULL;
        while(cur!=NULL)
        {
            next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        if(res)
            return true;
        else
            return false;
    }
};
4、链表形式的partition

给定一个链表和一个划分值,要求时间复杂度为O(N),空间复杂度为O(1)的条件下,将链表partition为小于划分值、等于划分值、大于划分值的三个部分,并且三个部分的内部的链表结点的相对顺序是不变的。
思路:

  • 首先设定6个变量,小于区域的头尾,等于区域的头尾,大于区域的头尾
  • 然后遍历链表
  • 如果该值小于划分值,那么将该值挂在小于区域的尾部,等于挂到等于区域的尾部,大于挂到大于区域的尾部
  • 然后再将这三个区域连接起来
注意:在将三个区域连接起来的时候,有些区域可能是没有值的,所以这个时候要注意多种情况,返回正确的链表的头部和首部。
struct linkedlist
{
	int value;
	linkedlist * next;
	linkedlist(int x) :value(x), next(NULL) {}
};
linkedlist* linkPartition(linkedlist* head, int target)
{
	linkedlist* ls=NULL;//less start
	linkedlist* le=NULL;//less end
	linkedlist* es=NULL;//equal start
	linkedlist* ee=NULL;//equal end
	linkedlist* ms=NULL;//more start
	linkedlist* me=NULL;//more end
	linkedlist* next = head;
	while (head != NULL)
	{
		next = head->next;
		head->next = NULL;
		if (head->value < target)
		{
			if (ls == NULL)
			{
				ls = head;
				le = head;
			}
			else
			{
				le->next = head;
				le = head;
			}
		}
		else if (head->value == target)
		{
			if (es == NULL)
			{
				es = head;
				ee = head;
			}
			else
			{

				ee->next = head;
				ee = head;
			}
		}
		else if (head->value > target)
		{
			if (ms == NULL)
			{
				ms = head;
				me = head;
			}
			else
			{
				me->next = head;
				me = head;
			}
		}
		head = next;
	}
	//逻辑是始终是ee连接ms
	if (ls != NULL)
	{
		le->next = es;
		ee = ee == NULL ? le : ee;
	}
	if (ee != NULL)
	{
		ee->next = ms;
	}
	return ls != NULL ? ls : (es != NULL ? es : ms);
}
5、复制有random指针的链表

描述:有random指针的链表也就是链表的节点除了next指针和value外,还有random指针,该指针可以指向自己或者链表中的任意一个结点,也可以是null。

任务:给定一个有random指针的链表,在不破坏原链表的前提下进行赋值得到一个一模一样的链表。

关键:问题的关键是怎样找到原结点与复制结点之间的对应关系,比如我们知道来了1结点的rand指向的是3,那么我们的问题关键就是如何通过原链表的节点信息知道3’结点在何处,从而将1’结点的random结点也设置为3’。也就是要找到原结点和复制节点之间的映射关系。

方法一:使用map,map的key为原结点,value为复制的节点,从而通过map找到原结点与复制接待之间的映射关系。此方法的时间复杂度为O(N),空间复杂度为O(N)。

方法二:将复制节点插入原来的链表中,复制节点为原结点的next值,这样就有了原结点和复制节点的映射关系。设置好复制节点的random指针之后,再将复制节点从原链表总分离出来,并且恢复原来的链表。该方法的空间复杂度为O(1),时间复杂度为O(N)。

代码:

struct linkedlist
{
	int value;
	linkedlist * next;
	linkedlist *random;
	linkedlist(int x) :value(x), next(NULL),random(NULL) {}
};
//使用方法一,map
linkedlist* list_copy(linkedlist* head)
{
	map<linkedlist*, linkedlist*> m;
	linkedlist* cur = head;
	while (cur != NULL)
	{
		m.insert(pair<linkedlist*, linkedlist*>(cur, new linkedlist(cur->value)));
		//插入的方法二
		//map[cur] = new linkedlist(cur->value);
		cur = cur->next;
	}
	//再遍历一遍链表进行赋值
	linkedlist* newhead = m[head];//新的链表的头
	linkedlist* next = NULL;
	linkedlist* random = NULL;
	while (head != NULL)
	{
		next = m[head->next];
		random = m[head->random];
		m[head]->next = next;
		m[head]->random = random;
		head = head->next;
	}
	return newhead;
}


//方法二,在原来的链表中插入新的复制的节点
linkedlist* listcopy(linkedlist* head)
{
	linkedlist* cur = head;
	linkedlist* next = NULL;
	linkedlist* random = NULL;
	linkedlist* tmp = NULL;
	//在原来的链表中插入新的节点
	while (cur != NULL)
	{
		next = cur->next;
		tmp = new linkedlist(cur->value);
		cur->next = tmp;
		tmp->next = next;
		cur = next;
	}
	//再进行一次遍历,设置复制节点的random指针
	cur = head;
	while (cur != NULL)
	{
		random = cur->random->next;
		cur->next->random = random;
		cur = cur->next->next;
	}
	//将赋值的链表从原来的链表中分离出来
	tmp = cur->next;//新链表的头部
	cur = head;
	while (cur != NULL)
	{
		next = cur->next;
		cur->next = next->next;
		next->next = cur->next->next;
		cur = cur->next;
	}
	return tmp;
}

6、循环链表

描述:给定两个可能有环也有可能无环的单链表,头结点head1和head2。实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交,返回NULL。

要求:如果两个链表的长度之和为N,时间复杂度为O(N),额外的空间复杂度为O(1)。

思路:首先判断两个链表是否有环,然后再判断两个链表是否有相交的部分。判断两个链表是否有环使用快慢指针。

判断是否有环:快慢指针,快指针一次走两步,慢指针一次走一步。在这个过程中只要遇到NULL,那么就说明该链表中没有环。如果有环,那么快慢指针一定会在环上相遇,相遇之后任选一个指针回到链表头,两一个指针留在原位。之后两个指针同时一次走一步,再次相遇的节点就是入环的节点。

判断两个链表是否有相同的节点:

  • 首先如果两个链表都没有环,那么首先判断两个链表最后一个非空节点是不是NULL,如果是那么两个链表相交=====》遍历两个链表得到长度,长链表先走差值步,之后一起走,得到第一个相遇的节点。如果最后一个非空节点不同,那么不相交。
  • 如果一个有环,一个没环,那么必定没有公共的部分
  • 如果两个都有环,那么假定第一个链表入环为节点loop1,链表二入环节点为loop2.有三种情况,量链表没有公共部分,两联表在环外相交并且共用环,两链表再换上相交(即同一个环,但是入环节点不同)。首先如果LOOP1==LOPP2,那么就是第二种情况,否则让LOOP1一种沿着next走,如果遇到loop2,那么就是情况三,否则不相交。
struct linkedlist
{
	int value;
	linkedlist * next;
	linkedlist(int x) :value(x), next(NULL) {}
};


//该函数接收链表的头,如果有环那么返回第一个入环的节点,没有环那么返回NULL
linkedlist* getLoopNode(linkedlist* head)
{
	if (head == NULL || head->next == NULL || head->next->next == NULL)
		return NULL;
	linkedlist* fast = NULL;
	linkedlist* slow = NULL;
	//快指针走两步,慢指针走一步
	slow = head->next;
	fast = head->next->next;
	while (fast != slow)
	{
		//判断是否还有两步可走,如果没有两步可走,那么一定没有环
		if (fast->next == NULL || fast->next->next == NULL)
			return NULL;
		fast = fast->next->next;
		slow = slow->next;
	}
	//相遇跳出循环,将快指针来到头结点
	fast = head;
	while (fast != slow)
	{
		fast = fast->next;
		slow = slow->next;
	}
	return fast;
}

//当两个链表都是无环的时候,判断是否有相交的节点
linkedlist* noLoop(linkedlist* head1, linkedlist* head2)
{
	linkedlist* cur1 = head1;
	linkedlist* cur2 = head2;
	int n = 0;
	//找到链表的最后一个不为空的节点
	while (cur1->next != NULL)
	{
		n++;
		cur1 = cur1->next;
	}
	while (cur2->next != NULL)
	{
		n--;
		cur2 = cur2->next;
	}
	if (cur1 != cur2)
		return NULL;
	//长链表为cur1,短链表为cue2
	cur1 = n >= 0 ? head1 : head2;
	cur2 = cur1 == head1 ? head2 : head1;
	n = abs(n);
	//长链表先走差值步
	while (n != 0)
	{
		n--;
		cur1 = cur1->next;
	}
	while (cur1 != cur2)
	{
		cur1 = cur1->next;
		cur2 = cur2->next;
	}
	return cur1;
}

//当两个链表都有环的时候
linkedlist* bothLoop(linkedlist* head1, linkedlist* head2, linkedlist* loop1, linkedlist* loop2)
{
	//两者共用环
	//相交节点在环外,转化Wie两个无环链表判断相交
	if (loop1 == loop2)
	{
		linkedlist* cur1 = head1;
		linkedlist* cur2 = head2;
		int n = 0;
		while (cur1 != loop1)
		{
			n++;
			cur1 = cur1->next;
		}
		while (cur2 != loop2)
		{
			n--;
			cur2 = cur2->next;
		}
		cur1 = n >= 0 ? head1 : head2;
		cur1 = cur1 == head1 ? head2 : head1;
		while (n != 0)
		{
			n--;
			cur1 = cur1->next;
		}
		while (cur1 != cur2)
		{
			cur1 = cur1->next;
			cur2 = cur2->next;
		}
		//环外的相交节点
		return cur1;
	}

	else
	{
		linkedlist* tmp = loop1->next;
		while (tmp != loop1)
		{
			if (tmp == loop2)
				return loop1;
			tmp = tmp->next;
		}
		//没有遇到loop2,说明没有公共的部分
		return NULL;
	}
}


//主函数的调用
bool is_common(linkedlist* head1, linkedlist* head2)
{
	linkedlist* loop1 = getLoopNode(head1);
	linkedlist* loop2 = getLoopNode(head2);
	if (loop1 == NULL&&loop2 == NULL)
		return noLoop(head1, head2);
	else
	{
		//一个有环,一个无环,一定没有公共部分
		if (loop1 == NULL || loop2 == NULL)
			return NULL;
		else
			return bothLoop(head1, head2, loop1, loop2);
	}
}

今天又是学习动力满满的一天
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值