python code practice(三):链表、栈、队列

1、删除链表中重复节点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5。

class Solution:
    def deleteDuplication(self, pHead):
        # write code here
        # 判断链表是否为空
        if not pHead:
            return None
        pPreNode = None #存储要删除节点的前一个节点
        pNode = pHead #定义起始节点
        #开始循环
        while pNode:
            pNext = pNode.next
            needDelete = False
            #判断相邻的两个节点值是否相等
            if pNext and pNext.val == pNode.val:
                needDelete = True
            #如果不相等就后移一个, pPreNode指向最前面的节点
            if not needDelete:
                pPreNode = pNode
                pNode = pNode.next
            #如果相等就开始删除
            else:
                val = pNode.val #初始化要删除节点的值
                pDeleteNode = pNode #定义要删除的节点
                #判断当前要删除的节点是否和val相等以及是否为空
                while pDeleteNode and pDeleteNode.val == val:
                    pNext = pDeleteNode.next
                    del pDeleteNode #删除节点
                    pDeleteNode = pNext #节点后移
                #如果要删除的节点包含初始的头结点,那就需要将头结点移到当前节点的位置
                if not pPreNode:
                    pHead = pNext
                #如果要删除的节点不包含头结点,则将节点进行连接
                else:
                    pPreNode.next = pNext
                #开始从最新的节点开始遍历
                pNode = pNext
        #返回头结点
        return pHead
class Solution:
    def deleteDuplicates(self, head: ListNode) -> ListNode:
        ans = head
        while head != None:
            if head.next != None and head.next.val == head.val:
                head.next = head.next.next
            else:
                head = head.next
        return ans
class Solution:
    def deleteDuplication(self, pHead):
        # write code here
        if not pHead or not pHead.next:#如果链表为空或只有一个元素,直接返回
            return pHead
        if pHead.val==pHead.next.val:#如果当前结点与下一结点重复,继续查找是否还有重复的
            p=pHead.next.next
            while p and p.val==pHead.val:#如果p结点不为空且其值与当前重复的结点(pHead)相等
                p=p.next#p继续往后移位查找,直到没有重复的
            return self.deleteDuplication(p)#递归调用函数对头结点为p的链表进行处理
        #如果当前结点与下一结点不重复,递归调用函数对头结点为pHead.next的链表进行处理
        pHead.next=self.deleteDuplication(pHead.next)
        return pHead

2、链表中环的入口节点

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

思路1:使用辅助存储空间。遍历链表,环的存在,遍历遇见的第一个重复的即为入口节点。

class Solution:
    def EntryNodeOfLoop(self, pHead):
        # write code here
        List=[]
        p=pHead
        while p:
            if p in List:
                return p
            else:
                List.append(p)
            p=p.next

思路2:

用快慢指针判断有没有环;

若有,返回快慢指针相遇时的指针,此时指针必定相遇在环中;

遍历环,得到环的数目n;

一个指针先走n步,另一个指针再开始走(它们速度相同),它们相遇的地方就是入口(解释:假设链表起始位置到环的入口节点距离为k,当后走的指针移动k步到达入口结点时,先走的指针移动距离为n+k,刚好多走了一个环的距离,所以又移动到了入口结点,此时两指针相遇)。

class Solution:
    def EntryNodeOfLoop(self, pHead):
        # write code here
        #判断是否有环,以及得到相遇节点
        meetingNode = self.MeetingNode(pHead)
        if not meetingNode:
            return None
        #得到环节点的数目
        nodenum = 1
        pNode = meetingNode
        while pNode.next != meetingNode:
            pNode = pNode.next
            nodenum += 1
        #寻找入口结点
        pNode1 = pHead
        for i in range(nodenum):
            pNode1 = pNode1.next
        pNode2 = pHead
        while pNode1 != pNode2:
            pNode1 = pNode1.next
            pNode2 = pNode2.next
        return pNode1
    def MeetingNode(self, pHead):
        if not pHead:
            return False
        fast = pHead
        slow = pHead
        while fast and fast.next and fast.next.next:
            fast = fast.next.next #之前写错成fast= phead.next.next
            slow = slow.next
            if fast == slow:
                return fast
        return False

关于快慢指针,通俗解释:

首先创建两个指针1和2(在java里就是两个对象引用),同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针1每次向下移动一个节点,让指针2每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环。

例如链表A->B->C->D->B->C->D,两个指针最初都指向节点A,进入第一轮循环,指针1移动到了节点B,指针2移动到了C。第二轮循环,指针1移动到了节点C,指针2移动到了节点B。第三轮循环,指针1移动到了节点D,指针2移动到了节点D,此时两指针指向同一节点,判断出链表有环。

此方法也可以用一个更生动的例子来形容:在一个环形跑道上,两个运动员在同一地点起跑,一个运动员速度快,一个运动员速度慢。当两人跑了一段时间,速度快的运动员必然会从速度慢的运动员身后再次追上并超过,原因很简单,因为跑道是环形的。
3、用两个栈实现队列

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

分析:

队列的特性是:“先入先出”,栈的特性是:“先入后出”

当我们向模拟的队列插入数 a,b,c 时,假设插入的是 stack1,此时的栈情况为:

  • 栈 stack1:{a,b,c}
  • 栈 stack2:{}

当需要弹出一个数,根据队列的"先进先出"原则,a 先进入,则 a 应该先弹出。但是此时 a 在 stack1 的最下面,将 stack1 中全部元素逐个弹出压入 stack2,现在可以正确的从 stack2 中弹出 a,此时的栈情况为:

  • 栈 stack1:{}
  • 栈 stack2:{c,b}

继续弹出一个数,b 比 c 先进入"队列",b 弹出,注意此时 b 在 stack2 的栈顶,可直接弹出,此时的栈情况为:

  • 栈 stack1:{}
  • 栈 stack2:{c}

此时向模拟队列插入一个数 d,还是插入 stack1,此时的栈情况为:

  • 栈 stack1:{d}
  • 栈 stack2:{c}

弹出一个数,c 比 d 先进入,c 弹出,注意此时 c 在 stack2 的栈顶,可直接弹出,此时的栈情况为:

  • 栈 stack1:{d}
  • 栈 stack2:{}

根据上述栗子可得出结论:

1.当插入时,直接插入 stack1.

2.当弹出时,当 stack2 不为空,弹出 stack2 栈顶元素,如果 stack2 为空,将 stack1 中的全部数逐个出栈,然后入栈 stack2,再弹出 stack2 栈顶元素。即stack1用作入队列,stack2用作出队列

代码很简单

# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.stack1 = []
        self.stack2 = []
        
    def push(self, node):
        # write code here
        self.stack1.append(node)
        
    def pop(self):
        # return xx
        if self.stack2 == []:
            while self.stack1:
                self.stack2.append(self.stack1.pop())
        return self.stack2.pop()

4、滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

先上代码:

class Solution:
    def maxInWindows(self, num, size):
        # write code here
        i = 0 #i表示当前窗口中的最后一个数字下标
        queue = [] #存放可能是最大值的元素的下标,注意存放的是下标。
        res = [] #存放最大值元素
        while size > 0 and i < len(num):
            #判断queue[0]是否还在当前窗口中
            if len(queue)>0 and i-size+1 > queue[0]:#仔细琢磨if中的第二个限制条件
                queue.pop(0) #如果queue非空,且queue中第一个下标即queue[0]小于当前窗口的最左端元素的下标索引,则删除queue[0].否则不执行出队操作。
            #如果有num[i]更大,那么目前队列中的队尾数字不可能成为最大值的下标,删除它
            while len(queue)>0 and num[queue[-1]] < num[i]:
                queue.pop()
            queue.append(i)
            #i=size-1时,第一个窗口建立完成,开始记录最大下标
            if i >= size - 1:
                res.append(num[queue[0]])
            i += 1
        return res

这道题目leetcode难度:困难。所以逻辑想清楚。这个解法用的双端队列思想。

关于这一句:

if len(queue)>0 and i-size+1 > queue[0]:

解释一下,顺便勾勒整个代码实现逻辑:

比如, {[2,3,4],2,6,2,5,1} ,第一个窗口尚未构建完成的时候,len(queue)>0好理解,下面分析:

i-size+1 > queue[0]

第一次滑动刚开始,第一个元素值是2,下标i=0,显然不满足上述if条件,同理也不满足接下来的两个条件,所以不执行对应语句,第一次只能执行的语句是:

queue.append(i)

i += 1

此时i更新为1,对应的元素值为3.继续新一轮条件判断,此时len(queue)=1,queue[0] =0,所以i-size+1 = 1-3+1 = -1,-1不满足>queue[0]的条件,故仍然不执行第一个条件下的语句,

以此类推,继续判断即可。

5、从尾到头打印链表

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

直接法:从头到尾遍历,然后逆序输出

class Solution:
    # 返回从尾部到头部的列表值序列,例如[1,2,3]
    def printListFromTailToHead(self, listNode):
        # write code here
        res = []
        while listNode:
            res.append(listNode.val)
            listNode = listNode.next
        return res[::-1]

跟上述方法逻辑一致,但是用栈再规范实现一遍:

class Solution:
    # 返回从尾部到头部的列表值序列,例如[1,2,3]
    def printListFromTailToHead(self, listNode):
        # write code here
        if not listNode:
            return []
        temp = []
        res = []
        while listNode:
            temp.append(listNode.val)
            listNode = listNode.next
        while temp:
            res.append(temp.pop())
        return res

来一个据说大部分人一看就会,一写就废的递归

class Solution:
    def printListFromTailToHead(self, listNode):
        res=[]
        def printListnode(listNode):
            # write code here
            if listNode:
                printListnode(listNode.next)#先递归到最后一层
                res.append(listNode.val)#添加值,退出函数,返回到上一层函数中的这行,继续添加值
        printListnode(listNode)
        return res

6.相交链表

编写一个程序,找到两个单链表相交的起始节点。

如下面的两个链表:

 

在节点 c1 开始相交。

示例 1

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

注意:

如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

分析:很到位

初始化 pA = headA, pB = headB,开始遍历。
以上图为例,pA会先到达链表尾,当pA到达末尾时,重置pA为headB;同样的,当pB到达末尾时,重置pB为headA。当pA与pB相遇时,必然就是两个链表的交点。(来个动态脑图,一目了然)

为什么要这样处理?因为这样的一个遍历过程,对pA而言,走过的路程即为a+c+b,对pB而言,即为b+c+a,显然a+c+b = b+c+a,这就是该算法的核心原理。

即使两个链表没有相交点,事实上,仍然可以统一处理,因为这种情况意味着相交点就是null,也就是上图中的公共部分c没有了,从而递推式变成了pA: a+b,pB: b+a,这同样是成立的。

code

class Solution(object):
    def getIntersectionNode(self, headA, headB):
        ha, hb = headA, headB
        while ha != hb:
            ha = ha.next if ha else headB
            hb = hb.next if hb else headA
        return ha

7. 用栈实现队列

使用栈实现队列的下列操作:

push(x) -- 将一个元素放入队列的尾部。
pop() -- 从队列首部移除元素。
peek() -- 返回队列首部的元素。
empty() -- 返回队列是否为空。
示例:

MyQueue queue = new MyQueue();

queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false
说明:

你只能使用标准的栈操作 -- 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)。

分析:

挺简单的,注意逻辑要严密。

class MyQueue:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.A = []
        self.B = []
        
    def push(self, x: int) -> None:
        """
        Push element x to the back of queue.
        """
        self.A.append(x)
        

    def pop(self) -> int:
        """
        Removes the element from in front of queue and returns that element.
        """
        if self.empty():
            return
        if len(self.B) == 0:
            while len(self.A) != 0:
                self.B.append(self.A.pop())
            return self.B.pop()
        else:
            return self.B.pop()
        
    def peek(self) -> int:
        """
        Get the front element.
        """
        if self.empty():
            return
        if len(self.B) == 0:
            while len(self.A) != 0:
                self.B.append(self.A.pop())
            return self.B[-1]
        else:
            return self.B[-1]

    def empty(self) -> bool:
        """
        Returns whether the queue is empty.
        """
        if len(self.B)==0 and len(self.A)==0:
            return True
        else:
            return False
原文地址:https://www.cnblogs.com/ariel-dreamland/p/12619401.html