堆栈、队列--Leetcode(python)

最小栈

设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。

push(x) – 将元素 x 推入栈中。
pop() – 删除栈顶的元素。
top() – 获取栈顶元素。
getMin() – 检索栈中的最小元素。
示例:

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.

解法:

  • 最简单的就可以想到使用列表。但是要实现检索最小值的功能,可以想到维护一个最小值,就像前缀树中维护一个word的布尔类型一样。注意随时更新min,可以加快运行速度。但是在pop的时候需要重新遍历栈来更新值,所以就不是O(1)的时间了
  • 辅助栈法:使用一个主栈模拟入栈出栈的操作,使用另一个栈来实现最小栈,每当入栈的数小于等于栈顶元素时,随主栈都进行一次 pushpush 操作;当出栈的数与最小栈的栈顶元素相等时,最小栈也随主栈进行一次 poppop 操作。

解法一:

class MinStack(object):

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.stack = []
        self.min = None
        

    def push(self, x):
        """
        :type x: int
        :rtype: None
        """
        self.stack.append(x)
        if self.min == None or self.min > x:
            self.min = x
        

    def pop(self):
        """
        :rtype: None
        """
        tmp = self.stack.pop()
        if len(self.stack) == 0:
            self.min = None
        
        if self.min == tmp:
            self.min = self.stack[0]
            for x in self.stack:
                if self.min > x:
                    self.min = x

    def top(self):
        """
        :rtype: int
        """
        return self.stack[-1]
        

    def getMin(self):
        """
        :rtype: int
        """
        return self.min
        

# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(x)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.getMin()

解法二:

class MinStack:

    def __init__(self):
        self.stack = []
        self.minstack = []

    def push(self, x: int) -> None:
        self.stack.append(x)
        if not self.minstack or x <= self.minstack[-1]:
            self.minstack.append(x)

    def pop(self) -> None:
        x = self.stack.pop()
        if x == self.minstack[-1]:
            self.minstack.pop()

    def top(self) -> int:
        return self.stack[-1]

    def getMin(self) -> int:
        return self.minstack[-1]

栈排序

栈排序。 编写程序,对栈进行排序使最小元素位于栈顶。最多只能使用一个其他的临时栈存放数据,但不得将元素复制到别的数据结构(如数组)中。该栈支持如下操作:push、pop、peek 和 isEmpty。当栈为空时,peek 返回 -1。

示例1:

输入:
[“SortedStack”, “push”, “push”, “peek”, “pop”, “peek”]
[[], [1], [2], [], [], []]
输出:
[null,null,null,1,null,2]
示例2:

输入:
[“SortedStack”, “pop”, “pop”, “push”, “pop”, “isEmpty”]
[[], [], [], [1], [], []]
输出:
[null,null,null,null,null,true]
说明:

栈中的元素数目在[0, 5000]范围内。

解法
用一个辅助栈

class SortedStack:

    def __init__(self):
        self.data = []
        self.helper = []

    def push(self, val: int) -> None:

        if not self.data: # 如果 data 为空,直接将元素添加到data中
            self.data.append(val)  
        else:
            # 如果 data 顶的元素比val小,将 data 中比 val 小的元素倒到 helper 中
            #然后将 val 放入 data 顶
            if self.data[-1] < val: 
                while self.data and self.data[-1] < val:
                    self.helper.append(self.data.pop(-1))
                self.data.append(val)
            # 如果 data 顶的元素等于 val,直接将 val 放在 data 顶
            elif self.data[-1] == val:
                self.data.append(val)
            else: 
                # 此时的情况为:val < sel.data[-1],需要把 helper 中比 val 大的元素倒到data顶去
                # case 1, 如果helper 为空,或者 val 大于等于 helper 顶的元素
                # 直接将val 放到 data 顶
                if not self.helper or self.helper[-1] <= val:
                    self.data.append(val)
                else:
                    # case 2, val 小于 helper 的栈顶元素,则把小于 val 的元素倒回 data 中
                    # 然后把 val 放在 data 栈顶
                    while self.helper and val < self.helper[-1]:
                        self.data.append(self.helper.pop())
                    self.data.append(val)

    def pop(self) -> None:
        if not self.data:
            return -1
        # 由于 helper 中会放有较小的元素,
        # 首先检查 helper 是否有元素,有的话将其倒入 data 中
        # pop data 顶的元素(当前最小值) 
        while self.helper:
            self.data.append(self.helper.pop())
        return self.data.pop()

    def peek(self) -> int:
        if not self.data:
            return -1
        while self.helper:
            self.data.append(self.helper.pop())
        return self.data[-1]

    def isEmpty(self) -> bool:
        return self.data == []

数据流的中位数

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例:

addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
进阶:

如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?
如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法?

解法
对于有序数组求中位数很简单了,但是题目给的是数据流,也就是我们必须做到可以时刻求出中位数的程度。

第一种方法:我们可以给每个从数据流出来的数进行排序,然后数组长度除以2就能得到中位数,但显然这样的做法是非常不好的,如果新进来的数是比当前排好序的所有元素都小的数,那么意味着数组中的所有数都必须向后移动一位,也就是O(n)的复杂度,假设每进来一个数都来一遍O(n),性能还是很低的。

第二种方法:采用优先队列,也就是大根堆跟小根堆,我们将较小的n/2个数放到大根堆中,将较大的n/2个数放到小根堆中,显然,如果n是偶数,那么大根堆的堆顶跟小根堆的堆顶就是我们要找的两个中位数,将其相加除以2作为结果返回即可。如果n是奇数,那么就看大根堆跟小根堆谁的节点个数比另一个堆多一个,节点数量多的那个堆的堆顶就是我们要找的中位数,此时我们直接返回结果即可。

需要注意的是:

1.对于取堆顶元素的操作的时间复杂度是常数级别的。

2.插入新节点时我们需要判断节点的值是否小于大根堆堆顶的值或者大于小根堆堆顶的值,

如果小于大根堆堆顶的值,那么节点应该插入大根堆,反过来应该插入小根堆。

3.每次插入新节点我们还需要判断两个堆之间的元素个数是否平衡。插入新节点后,我们判断两个堆的元素个数,如果相差为2那么我们就要对堆进行调整。比如新插入一个节点到小根堆中,而此时大根堆的个数+1小于小根堆的节点个数,这个时候只需要将小根堆的堆顶元素弹出,然后将这个弹出的元素插入大根堆即可。反过来也是一样的操作。为什么可以这样做呢?这是因为我们说了小根堆保存的是较大的n/2个数,而小根堆的堆顶是小根堆中最小的元素,同时也是大根堆中最大的元素,因此我们将这个堆顶元素弹出并插入大根堆的操作并不会破坏“小根堆保存较大的n/2个数,大根堆保存较小的n/2”这样的前提。

class MedianFinder:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.max_heap = []
        self.min_heap = []

    def addNum(self, num):
        if not self.max_heap:
            heapq.heappush(self.max_heap, -num)  # python的heaqp模块是小根堆,因此要保存大根堆的话需要加上一个负号
            return

        if num > -self.max_heap[0]:
            heapq.heappush(self.min_heap, num)
            if len(self.max_heap) + 1 < len(self.min_heap):
                heapq.heappush(self.max_heap, -heapq.heappop(self.min_heap))  # 插入大根堆需要给值加负号
        else:
            heapq.heappush(self.max_heap, -num)
            if len(self.max_heap) > len(self.min_heap) + 1:
                heapq.heappush(self.min_heap, -heapq.heappop(self.max_heap))  # 弹出大根堆堆顶的时候需要加负号变为正数

    def findMedian(self):
        if len(self.max_heap) > len(self.min_heap):   # 大根堆元素多一个,中位数是大根堆堆顶
            return -self.max_heap[0]
        if len(self.max_heap) < len(self.min_heap):   # 小根堆元素多一个,中位数是小根堆堆顶
            return self.min_heap[0]
        else:
            return (-self.max_heap[0] + self.min_heap[0])/2
        


# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()

数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

解法

  • 最简单的就是给数组排序,求k这个Index位置上的数
  • 使用快速排序思想,就是找第k个pointer。注意是从左到右从大到小排序,使用partition比较简单些。
  • 递归,就是麻烦一点,和解法二差不多
class Solution(object):
    def findKthLargest(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        return self.quick(nums, 0, len(nums)-1, k-1)
    
    def quick(self, nums, l, r, k):
        if l == r:
            return nums[l]
        
        p = self.partition(nums, l, r)
        
        if p == k:
            return nums[p]
        elif k < p:
            return self.quick(nums, l, p-1, k)
        else:
            return self.quick(nums, p+1, r, k)
        
    def partition(self, nums, l, r):
        j = l
        for i in range(l+1, r+1):
            if nums[i] > nums[l]:
                j+=1
                nums[i], nums[j] = nums[j], nums[i]
                
        nums[j], nums[l] = nums[l], nums[j]
        
        return j
        
            

解法3:

class Solution:
    def _partition(self, nums, l, r):
        j = l
        for i in range(l + 1, r + 1):
            if nums[i] > nums[l]:
                j += 1
                nums[i], nums[j] = nums[j], nums[i]
        nums[l], nums[j] = nums[j], nums[l]
        return j

    def findKthLargest(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        l, r, k = 0, len(nums) - 1, k - 1
        while 1:
            p = self._partition(nums, l, r)

            if k == p:
                return nums[p]
            elif k < p:
                r = p - 1
            else:
                l = p + 1

有序矩阵中第K小的元素

给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第k小的元素。
请注意,它是排序后的第k小元素,而不是第k个元素。

示例:

matrix = [
[ 1, 5, 9],
[10, 11, 13],
[12, 13, 15]
],
k = 8,

返回 13。
说明:
你可以假设 k 的值永远是有效的, 1 ≤ k ≤ n2 。

解法:

  • 利用最大堆,建一个大小为k的最大堆,遍历矩阵,将矩阵元素一一插入到堆中,堆的大小超过k后要弹出堆顶元素。这样做时间复杂度为O(N2logk)。但是这种解法没有利用到矩阵行和列分别有序的信息。
  • 可以用二分查找法来做,我们由于是有序矩阵,那么左上角的数字一定是最小的,而右下角的数字一定是最大的,所以这个是我们搜索的范围。然后我们算出中间数字mid,由于矩阵中不同行之间的元素并不是严格有序的,所以我们要在每一行都查找一下mid。
    1.首先设置mid的初值为矩阵matrix,最后一个数和第一个数的平均值。
    2.统计矩阵中所有行小于mid值的个数之和。若cnt小于8,L = mid + 1,并更新mid;若该值大于8,R = mid。
    3.重复上述搜索,直至L和R不满足L < R,此时的L或者R值即为所求。
class Solution(object):
    def kthSmallest(self, matrix, k):
        """
        :type matrix: List[List[int]]
        :type k: int
        :rtype: int
        """
        a, b = len(matrix), len(matrix[0])
        left = matrix[0][0]
        right = matrix[a-1][b-1]
        
        while left < right:
            mid = left + (right - left) / 2
            cnt = 0
            for i in range(a):
                j = b-1
                while j >=0 and matrix[i][j] > mid:
                    j -= 1
                cnt += j+1
                
            if cnt < k:
                left = mid + 1
            else:
                right = mid
                
        return left

滑动窗口最大值

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。
示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

解法
Sliding Window法。
维护window这个数组,使得在任意时刻,window【0】都是当前Window内最大值的下标。
并且window从左到右恰恰是第一大、第二大、。。。。。第K大的元素(也就是C++中的双端队列,在队列中存储元素在数组中的位置, 并且维持队列的严格递减)
维护过程为:

  1. 如果window不为空,并且 window[0] 比 i - k 小 (这就说明window[0]在当前Window的左界的左侧,应该被踢出去), 把window【0】弄出去
  2. 把从队尾倒着数的每个比item 大的元素都弄出去
  3. 把item 弄进Window
  4. 如果index > = k - 1, 就说明Window size 已经有k了,可以输出答案了。因为如果 index < k - 1, 说明Window size还没到k。
class Solution(object):
    def maxSlidingWindow(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """
        if not nums:
            return []
        
        res, window = [], []
        
        for index, item in enumerate(nums):
            if window and window[0] <= index - k:
                window.pop(0)
                
            while window and nums[window[-1]] <= item:
                window.pop()
                
            window.append(index)
            
            if index >= k-1:
                res.append(nums[window[0]])
                
        return res

基本计算器 II

实现一个基本的计算器来计算一个简单的字符串表达式的值。

字符串表达式仅包含非负整数,+, - ,*,/ 四种运算符和空格 。 整数除法仅保留整数部分。

示例 1:

输入: “3+2*2”
输出: 7
示例 2:

输入: " 3/2 "
输出: 1
示例 3:

输入: " 3+5 / 2 "
输出: 5
说明:

你可以假设所给定的表达式都是有效的。
请不要使用内置的库函数 eval。

解法
计算器的实现一般是使用压栈的方式,但此处有几个注意事项:

  1. 因为包含加减乘除,一般是对乘法和除法特殊处理(括号也是这样先处理)压入栈,然后再全体加起来。
  2. 一般都是使用整数栈,不要再把符号压入栈了,所以对于减法只需把数字加个负号。
  3. 注意题目中并没有说是十以内加减法,所以有可能出现多位数,也就是要处理数字,用num作为当前的数字,直到遇见符号的时候这个数字就结束了。
  4. 由于需要用下一个符号来判断前一个数字是否结束,所以用sign来代表前一个符号。
  5. 两个整数直接相除得到的结果是自动向上取整的,比如3/2=2,所以可以先转换成浮点数再取整。
class Solution(object):
    def calculate(self, s):
        """
        :type s: str
        :rtype: int
        """
        s = s.replace(' ', '')
        res = []
        num = 0
        sign = '+'
        for idx, x in enumerate(s):
            if x.isdigit():
                num = num * 10 + int(x)
            if x.isdigit() == False or idx == len(s) - 1:
                if sign == '+':
                    res.append(num)
                elif sign == '-':
                    res.append(-num)
                elif sign == '*':
                    res.append(res.pop() * num)
                elif sign == '/':
                    res.append(int(float(res.pop())/float(num)))

                num = 0
                sign = x

        out = 0
        for idx, x in enumerate(res):
            out += x

        return out
            
                

逆波兰表达式求值

根据逆波兰表示法,求表达式的值。

有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:

整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:

输入: [“2”, “1”, “+”, “3”, “*”]
输出: 9
解释: ((2 + 1) * 3) = 9
示例 2:

输入: [“4”, “13”, “5”, “/”, “+”]
输出: 6
解释: (4 + (13 / 5)) = 6

示例 3:

输入: [“10”, “6”, “9”, “3”, “+”, “-11”, “", “/”, "”, “17”, “+”, “5”, “+”]
输出: 22
解释:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

解法
和计算器的方法相同,都是压栈,这个更简单,因为是后缀表达式的形式,而且不需要判断当前数字是否已经结束,因为用列表给出了。
但是要注意不能用.isdigit去判断是否是数字,因为这只能判断0-9的数字,当遇到”-11“这种就错了。

class Solution(object):
    def evalRPN(self, tokens):
        """
        :type tokens: List[str]
        :rtype: int
        """
        res = []
        for idx, x in enumerate(tokens):
            if x != '+' and x != '-' and x != "*" and x != "/":
                res.append(int(x))
            else:
                b=res.pop()
                a=res.pop()
                tmp = 0
                if x == '+':
                    tmp=a+b
                elif x == '-':
                    tmp = a - b
                elif x == '*':
                    tmp = a * b
                elif x == '/':
                    tmp = int(float(a) / float(b))
                
                res.append(tmp)
                
        return res[-1]

打开转盘锁

你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。

锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。

列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。

字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。

示例 1:

输入:deadends = [“0201”,“0101”,“0102”,“1212”,“2002”], target = “0202”
输出:6
解释:
可能的移动序列为 “0000” -> “1000” -> “1100” -> “1200” -> “1201” -> “1202” -> “0202”。
注意 “0000” -> “0001” -> “0002” -> “0102” -> “0202” 这样的序列是不能解锁的,
因为当拨动到 “0102” 时这个锁就会被锁定。
示例 2:

输入: deadends = [“8888”], target = “0009”
输出:1
解释:
把最后一位反向旋转一次即可 “0000” -> “0009”。
示例 3:

输入: deadends = [“8887”,“8889”,“8878”,“8898”,“8788”,“8988”,“7888”,“9888”], target = “8888”
输出:-1
解释:
无法旋转到目标数字且不被锁定。
示例 4:

输入: deadends = [“0000”], target = “8888”
输出:-1

提示:

死亡列表 deadends 的长度范围为 [1, 500]。
目标数字 target 不会在 deadends 之中。
每个 deadends 和 target 中的字符串的数字会在 10,000 个可能的情况 ‘0000’ 到 ‘9999’ 中产生。

解法
首先是求“最小”次数,所以用BFS。本来用了vis另一个set保存遍历过的元素,但是发现其实可以和deadends合并,省时间和空间。
注意每次加入“树的子节点”的时候,都是加入8个(4位数,8种改变的可能)

class Solution(object):
    def openLock(self, deadends, target):
        """
        :type deadends: List[str]
        :type target: str
        :rtype: int
        """
        ori = "0000"
        if ori in deadends:
            return -1
        
        q = [(ori, 0)]
        deadends = set(deadends)
        
        while q:
            top, step = q.pop(0)
            if top == target:
                return step
            for i in range(4):
                x = [(int(top[i]) + 1) % 10, (int(top[i]) - 1) % 10]
                for j in range(2):
                    k = str(x[j])
                    cur = top[:i] + k + top[i+1:]
                    if cur not in deadends:
                        deadends.add(cur)
                        q.append((cur, step+1))
                
                        
        return -1

有效的括号

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

示例 1:

输入: “()”
输出: true
示例 2:

输入: “()[]{}”
输出: true
示例 3:

输入: “(]”
输出: false
示例 4:

输入: “([)]”
输出: false
示例 5:

输入: “{[]}”
输出: true

解法
首先读懂题目,判断括号是否有效;然后找到有效的条件:

  • 左右括号匹配:存在右括号,就找左括号是否存在
  • 顺序不能错:存在右括号时,pop出来的第一个括号就应该是对应的左括号。
  • 空字符串
class Solution(object):
    def isValid(self, s):
        """
        :type s: str
        :rtype: bool
        """
        if not s:
            return True
        
        stack = []
        for x in s:
            #print(x)
            if x == "(" or x == "{" or x == "[":
                stack.append(x)
            if x == ')':
                if not stack or stack.pop() != '(':
                    return False
            if x == '}':
                if not stack or stack.pop() != '{':
                    return False
            if x == ']':
                if not stack or stack.pop() != '[':
                    return False
                
        if not stack:
            return True
        return False
    

每日温度

根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

解法
题意:遍历列表,得到最近的比当前值大的索引差值。分析可能的情况:

  • i+1位置就比i位置值大(比如[73, 74]),res[i]=1
  • i+1位置小于i位置,但是i+n位置大于i位置(比如[71, 69, 72]),res[i]=n
  • 后面没有比i位置更大的数,res[i]=0

那么就可以想到用栈保存前面的数的位置,遍历当前数x的时候,就pop出最近的数比较一下是否比x小,如果是的话,取出来给res赋值(索引差);如果不是的话,stack前面不可能有比x更小的

class Solution(object):
    def dailyTemperatures(self, T):
        """
        :type T: List[int]
        :rtype: List[int]
        """
        res = [0] * len(T)
        
        stack = [0]
        for i in range(1, len(T)):
            while len(stack) > 0 and T[stack[-1]] < T[i]:
                idx = stack.pop()
                res[idx] = i - idx
            stack.append(i)
            
        return res

克隆图

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。

图中的每个节点都包含它的值 val(int) 和其邻居的列表(list[Node])。

class Node {
public int val;
public List neighbors;
}

测试用例格式:

简单起见,每个节点的值都和它的索引相同。例如,第一个节点值为 1,第二个节点值为 2,以此类推。该图在测试用例中使用邻接列表表示。

邻接列表是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。

给定节点将始终是图中的第一个节点(值为 1)。你必须将 给定节点的拷贝 作为对克隆图的引用返回。

示例 1:
输入:adjList = [[2,4],[1,3],[2,4],[1,3]]
输出:[[2,4],[1,3],[2,4],[1,3]]
解释:
图中有 4 个节点。
节点 1 的值是 1,它有两个邻居:节点 2 和 4 。
节点 2 的值是 2,它有两个邻居:节点 1 和 3 。
节点 3 的值是 3,它有两个邻居:节点 2 和 4 。
节点 4 的值是 4,它有两个邻居:节点 1 和 3 。

示例 2:
输入:adjList = [[]]
输出:[[]]
解释:输入包含一个空列表。该图仅仅只有一个值为 1 的节点,它没有任何邻居。

示例 3:
输入:adjList = []
输出:[]
解释:这个图是空的,它不含任何节点。

示例 4:
输入:adjList = [[2],[1]]
输出:[[2],[1]]

提示:

节点数介于 1 到 100 之间。
每个节点值都是唯一的。
无向图是一个简单图,这意味着图中没有重复的边,也没有自环。
由于图是无向的,如果节点 p 是节点 q 的邻居,那么节点 q 也必须是节点 p 的邻居。
图是连通图,你可以从给定节点访问到所有节点。

解法
题意:使用图的起始节点重新构建一个相同的图。情况分析:

  • 图是空的(没有节点)
  • 图上只有一个节点(注意要返回节点,而不是返回空)
  • 如果用队列来做的话:也就是BFS遍历,当遍历到当前节点ori,需要找到对应的新建的图上的该节点cur才能给cur赋值邻居,因此需要一个字典存储,该字典还可以用于判断ori的邻居是否在新图上已经存在;为了避免重复遍历节点(因为邻居可能重复),需要一个visit存储;为了避免重复把同一个未遍历过的邻居节点加入队列,需要判断队列中是否已经存在。
"""
# Definition for a Node.
class Node(object):
    def __init__(self, val = 0, neighbors = []):
        self.val = val
        self.neighbors = neighbors
"""
class Solution(object):
    def cloneGraph(self, node):
        """
        :type node: Node
        :rtype: Node
        """
        if not node:
            return None
        if not node.neighbors:
            return Node(val=node.val)
        
        root = Node(val=node.val)
        dic = {1: root}
        q = [node]
        vis = set()
        
        while q:
            ori = q.pop(0)
            cur = dic[ori.val]
            #print(ori.val, cur.val)
            for x in ori.neighbors:
                #print(x.val, dic.keys(), vis)
                if x.val not in dic:
                    tmp = Node(x.val)
                    dic[x.val] = tmp
                cur.neighbors.append(dic[x.val])
                if x.val not in vis and x not in q:
                    q.append(x)
                    
            vis.add(ori.val)
            
        return root
            
        

目标和

给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例 1:

输入: nums: [1, 1, 1, 1, 1], S: 3
输出: 5
解释:

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

一共有5种方法让最终目标和为3。
注意:

数组非空,且长度不会超过20。
初始的数组的和不会超过1000。
保证返回的最终结果能被32位整数存下。

解法
题意:返回加减数组中每个数字和为目标数 S 的所有可能性。(如果用动态规划的思路的话:在背包问题中,我们要考虑物品放还是不放)
很快想到用DFS,定义函数f(i,target)表示i长度内目标为target的方法数,那么f(i,target)=f(i−1,target−nums[i])+f(i−1,target+nums[i])。直接这样做容易超时,所以可以想到用一个字典保存下来已经遍历过的方法数目。

class Solution(object):
    def findTargetSumWays(self, nums, S):
        """
        :type nums: List[int]
        :type S: int
        :rtype: int
        """
        
        vis = {(0, 0): 1}
        
        res = self.dfs(nums, S, vis)
        
        return res
    
    def dfs(self, nums, S, vis):
        if (len(nums), S) in vis:
            return vis[(len(nums), S)]
        elif len(nums) == 0:
            return 0
        
        vis[(len(nums), S)] = self.dfs(nums[1:], S - nums[0], vis) + self.dfs(nums[1:], S + nums[0], vis)
        
        return vis[(len(nums), S)]

二叉树的中序遍历

给定一个二叉树,返回它的中序 遍历。

示例:

输入: [1,null,2,3]
   1
    \
     2
    /
   3

输出: [1,3,2]

输出: [1,3,2]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?

解法
使用迭代方法

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):
    def inorderTraversal(self, root):
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        if root == None:
            return []
        
        res = []
        cur = root
        stack = []
        
        while stack or cur:
            if cur:
                stack.append(cur)
                cur = cur.left
            else:
                cur = stack.pop()
                res.append(cur.val)
                cur = cur.right
                
        return res
        

字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

示例:

s = “3[a]2[bc]”, 返回 “aaabcbc”.
s = “3[a2[c]]”, 返回 “accaccacc”.
s = “2[abc]3[cd]ef”, 返回 “abcabccdcdcdef”.

解法
题意:类似于计算器,返回k*字符串内部子串的结果
思路:遇到不同类型的字符进行不同方式处理,几种情况:

  • 数字:注意可能出现多位数
  • 字母:拼成tmp子串,用于后续的重复
  • 字符“]":将重复后的tmp子串再放入栈
  • 字符"[":重置数字、子串等

自己写的,比较麻烦:

class Solution(object):
    def decodeString(self, s):
        """
        :type s: str
        :rtype: str
        """
        if not s:
            return ""
        stack = []
        for i, x in enumerate(s):
            if x != "]":
                stack.append(x)
            else:
                tmp = []
                while stack[-1] != "[":
                    tmp.append(stack.pop())
                tmp = tmp[::-1]
                stack.pop()
                
                k = ""
                while len(stack) > 0 and stack[-1].isdigit():
                    k = stack.pop() + k
                k = int(k)
                while k > 0:
                    stack.extend(tmp)
                    k -= 1
                    
        return "".join(stack)

网上看的比较清晰简洁的方法:

class Solution(object):
    def decodeString(self, s):
        """
        :type s: str
        :rtype: str
        """
        stack = []
        curNum = 0
        curString = ''
        for c in s:
            if c == '[':
                stack.append(curString)
                stack.append(curNum)
                curString = ''
                curNum = 0
            elif c == ']':
                num = stack.pop()
                prevString = stack.pop()
                curString = prevString + num * curString
            elif c.isdigit():
                curNum = curNum * 10 + int(c)
            else:
                curString += c
        return curString

图像渲染

有一幅以二维整数数组表示的图画,每一个整数表示该图画的像素值大小,数值在 0 到 65535 之间。

给你一个坐标 (sr, sc) 表示图像渲染开始的像素值(行 ,列)和一个新的颜色值 newColor,让你重新上色这幅图像。

为了完成上色工作,从初始坐标开始,记录初始坐标的上下左右四个方向上像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应四个方向上像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为新的颜色值。

最后返回经过上色渲染后的图像。

示例 1:

输入:
image = [[1,1,1],[1,1,0],[1,0,1]]
sr = 1, sc = 1, newColor = 2
输出: [[2,2,2],[2,2,0],[2,0,1]]
解析:
在图像的正中间,(坐标(sr,sc)=(1,1)),
在路径上所有符合条件的像素点的颜色都被更改成2。
注意,右下角的像素没有更改为2,
因为它不是在上下左右四个方向上与初始点相连的像素点。

注意:
image 和 image[0] 的长度在范围 [1, 50] 内。
给出的初始点将满足 0 <= sr < image.length 和 0 <= sc image[i][j] 和 newColor 表示的颜色值在范围 [0, 65535]内。

解法
很显然就是”迷宫“类问题,用dfs即可。也可以用队列解决

dfs方法:

class Solution(object):
    def floodFill(self, image, sr, sc, newColor):
        """
        :type image: List[List[int]]
        :type sr: int
        :type sc: int
        :type newColor: int
        :rtype: List[List[int]]
        """
        if not image:
            return image
        
        curC = image[sr][sc]
        self.vis = [[False for i in range(len(image[0]))] for j in range(len(image))]
        image = self.dfs(image, sr, sc, curC, newColor)
        
        return image
        
    def dfs(self, image, r, c, curC, newC):
        image[r][c] = newC
        self.vis[r][c] = True
            
        if r > 0 and image[r - 1][c] == curC and not self.vis[r - 1][c]:
            image = self.dfs(image, r - 1, c, curC, newC)
            
        if r < len(image) - 1 and image[r + 1][c] == curC and not self.vis[r + 1][c]:
            image = self.dfs(image, r + 1, c, curC, newC)
            
        if c > 0 and image[r][c - 1] == curC and not self.vis[r][c - 1]:
            image = self.dfs(image, r, c - 1, curC, newC)
            
        if c < len(image[0]) - 1 and image[r][c + 1] == curC and not self.vis[r][c + 1]:
            image = self.dfs(image, r, c + 1, curC, newC)
            
        return image
            

队列:

class Solution(object):
    def floodFill(self, image, sr, sc, newColor):
        """
        :type image: List[List[int]]
        :type sr: int
        :type sc: int
        :type newColor: int
        :rtype: List[List[int]]
        """
        m, n = len(image), len(image[0])
        color = image[sr][sc]
        image[sr][sc] = newColor
        
        visited = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
        dx = [1, -1, 0, 0]
        dy = [0, 0, 1, -1]
        
        q = deque()
        q.append([sr,sc])
        while q:
            x0, y0 = q.popleft()
            for k in range(4):
                x = x0 + dx[k]
                y = y0 + dy[k]
 
                if 0 <= x < m and 0 <= y < n and image[x][y] == color and visited[x][y] == 0:
                    image[x][y] = newColor
                    visited[x][y] = 1
                    q.append([x, y])
                    
        return image

01 矩阵

给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。

两个相邻元素间的距离为 1 。

示例 1:
输入:

0 0 0
0 1 0
0 0 0
输出:

0 0 0
0 1 0
0 0 0
示例 2:
输入:

0 0 0
0 1 0
1 1 1
输出:

0 0 0
0 1 0
1 2 1

注意:
给定矩阵的元素个数不超过 10000。
给定矩阵中至少有一个元素是 0。
矩阵中的元素只在四个方向上相邻: 上、下、左、右。

解法
题意:找离每个1最近的0的距离
步骤:因为是最近距离,显然是BFS。开始想的方法是遍历矩阵,找到每个1之后再用BFS,显然复杂度非常高。后来看到网上的做法,其实这就相当于求每个0最近的1的距离,遍历一遍矩阵就可以了。

class Solution(object):
    def updateMatrix(self, matrix):
        """
        :type matrix: List[List[int]]
        :rtype: List[List[int]]
        """
        
        if not matrix:
            return matrix
        
        res = [[0 for i in range(len(matrix[0]))] for j in range(len(matrix))]
        vis = [[False for ii in range(len(matrix[0]))] for jj in range(len(matrix))]
        queue = []
        for i in range(len(matrix)):
            for j in range(len(matrix[0])):
                if matrix[i][j] == 0:
                    queue.append((i, j, 0))
                    vis[i][j] = True
        
        dx = [1, -1, 0, 0]
        dy = [0, 0, 1, -1]
        while queue:
            x0, y0, step = queue.pop(0)
            if matrix[x0][y0] == 1:
                res[x0][y0] = step
                
            step += 1
            for k in range(4):
                x = x0 + dx[k]
                y = y0 + dy[k]

                if x >= 0 and x <= len(matrix) - 1 and y >= 0 and y <= len(matrix[0]) - 1 and vis[x][y] == False:
                    queue.append((x, y, step))
                    vis[x][y] = True
                        
        return res                
                        

钥匙和房间

有 N 个房间,开始时你位于 0 号房间。每个房间有不同的号码:0,1,2,…,N-1,并且房间里可能有一些钥匙能使你进入下一个房间。

在形式上,对于每个房间 i 都有一个钥匙列表 rooms[i],每个钥匙 rooms[i][j] 由 [0,1,…,N-1] 中的一个整数表示,其中 N = rooms.length。 钥匙 rooms[i][j] = v 可以打开编号为 v 的房间。

最初,除 0 号房间外的其余所有房间都被锁住。

你可以自由地在房间之间来回走动。

如果能进入每个房间返回 true,否则返回 false。

示例 1:

输入: [[1],[2],[3],[]]
输出: true
解释:
我们从 0 号房间开始,拿到钥匙 1。
之后我们去 1 号房间,拿到钥匙 2。
然后我们去 2 号房间,拿到钥匙 3。
最后我们去了 3 号房间。
由于我们能够进入每个房间,我们返回 true。
示例 2:

输入:[[1,3],[3,0,1],[2],[0]]
输出:false
解释:我们不能进入 2 号房间。

提示:
1 <= rooms.length <= 1000
0 <= rooms[i].length <= 1000
所有房间中的钥匙数量总计不超过 3000。

解法
题意:判断是否每个房间都被visited过
方法:用队列或者栈应该都可以。

class Solution(object):
    def canVisitAllRooms(self, rooms):
        """
        :type rooms: List[List[int]]
        :rtype: bool
        """
        if not rooms:
            return rooms
        
        queue = []
        vis = [False for i in range(len(rooms))]
        vis[0] = True
        for x in rooms[0]:
            queue.append(x)
            vis[x] = True
            
        while queue:
            top = queue.pop(0)
            for x in rooms[top]:
                if vis[x] == False:
                    queue.append(x)
                    vis[x] = True
                    
        for i in range(len(vis)):
            if vis[i] == False:
                return False
        
        return True

用两个栈实现队列

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

解法
开始没怎么看明白题目,所以看了题解。栈A用来作入队列。栈B用来出队列,当栈B为空时,栈A全部出栈到栈B,栈B再出栈(即出队列)。这个想法还是比较巧妙的。

# -*- 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()

包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。

解法
其实就是提前存下一个min值,随时更新它就可以。

# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.stack = []
        self.mi = 100000
        
    def push(self, node):
        # write code here
        self.stack.append(node)
        if node < self.mi:
            self.mi = node
            
    def pop(self):
        # write code here
        t = self.stack[-1]
        self.stack = self.stack[:-1]
        if t == self.mi:
            self.mi = 100000
            for x in self.stack:
                if x < self.mi:
                    self.mi = x
                
        return t
    
    def top(self):
        # write code here
        t = self.stack[-1]
        return t
    
    def min(self):
        # write code here
        return self.mi
        

字符流中第一个不重复的字符

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。

解法
想到了哈希,用一个列表存储所有进来的字符,随时判断每个字符是否重复,并且随时更新当前指向第一个不重复字符的指针。
但是后来看了解析,发现并不需要这么复杂,可以使用队列的思想,不需要保存下所有字符,只需要保存那些不重复的字符。

  • 入队:获取字符流中的一个字符时,当我们判断它是不重复时,将它加入队列;
  • 输出/出队:注意,因为队列中存储的 “不重复字符” 在一系列的流读取操作后,随时有可能改变状态(变重复),所以,队列中的字符不能直接输出,要先进行一次重复判断,如果发现队头字符已经重复了,就将它移出队列并判断新的队头,否则,输出队头的值;
# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.s = []
        self.dic = {}
        self.idx = -1
    # 返回对应char
    def FirstAppearingOnce(self):
        # write code here
        if self.idx == -1:
            return "#"
        return self.s[self.idx]
    
    def Insert(self, char):
        # write code here
        self.s.append(char)
        if char not in self.dic:
            self.dic[char] = 1
            if self.idx == -1:
                self.idx = len(self.s) - 1
        else:
            self.dic[char] += 1
            
        if self.idx != -1 and self.dic[self.s[self.idx]] > 1:
            flag = 0
            for i in range(self.idx + 1, len(self.s)):
                if self.dic[self.s[i]] == 1:
                    self.idx = i
                    flag = 1
                    break
            if flag == 0:
                self.idx = -1
                    

栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

解法
链接:https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106?answerType=1&f=discussion
来源:牛客网

直接模拟即可。因为弹出之前的值都会先入栈,所以这里用个栈来辅助。

  • 初始化:用指针i指向pushV的第一个位置, 指针j指向popV的第一个位置;
  • 如果pushV[i] != popV[j], 那么应该将pushV[i]放入栈中, ++i
  • 否则,pushV[i]==popV[j], 说明这个元素是放入栈中立马弹出,所以,++i, ++j,然后应该检查popV[j],与栈顶元素是否相等,如果相等,++j, 并且弹出栈顶元素
  • 重复2,3, 如果i==pushV.size(), 说明入栈序列访问完,此时检查栈是否为空,如果为空,说明匹配,斗则不匹配。
# -*- coding:utf-8 -*-
class Solution:
    def IsPopOrder(self, pushV, popV):
        # write code here
        l, r = 0, 0
        stack = []
        while pushV:
            tmp = pushV.pop(0)
            if tmp != popV[0]:
                stack.append(tmp)
            else:
                popV.pop(0)
                if not popV:
                    return True
                while stack[-1] == popV[0]:
                    stack.pop(-1)
                    popV.pop(0)
                    if not popV:
                        return True
        
        return False

单词接龙

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:

每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:

如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:

输入:
beginWord = “hit”,
endWord = “cog”,
wordList = [“hot”,“dot”,“dog”,“lot”,“log”,“cog”]

输出: 5

解释: 一个最短转换序列是 “hit” -> “hot” -> “dot” -> “dog” -> “cog”,
返回它的长度 5。
示例 2:

输入:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,“dot”,“dog”,“lot”,“log”]

输出: 0

解释: endWord “cog” 不在字典中,所以无法进行转换。

解法
由于是求最短路径,我们很容易想到通过广度优先遍历来解决这个问题。现在我们要解决的问题就变成了如何判断两个单词只有一个字母不同。最简单的办法就是通过26个字母替换:
其他优化方法:https://blog.csdn.net/qq_17550379/article/details/83652490

class Solution(object):
    def ladderLength(self, beginWord, endWord, wordList):
        """
        :type beginWord: str
        :type endWord: str
        :type wordList: List[str]
        :rtype: int
        """
        wordDict = set(wordList)
        if endWord not in wordList:
            return 0
        
        q = [(beginWord, 1)]
        visited = set()
        while q:
            word, step = q.pop(0)
            if word not in visited:
                visited.add(word)
                if word == endWord:
                    return step
                
                for i in range(len(word)):
                    for j in 'abcdefghijklmnopqrstuvwxyz':
                        tmp = word[:i] + j + word[i+1:]
                        if tmp not in visited and tmp in wordDict:
                            q.append((tmp, step + 1))
                            
        return 0
        

单词接龙 II

给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:

每次转换只能改变一个字母。
转换后得到的单词必须是字典中的单词。
说明:

如果不存在这样的转换序列,返回一个空列表。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:

输入:
beginWord = “hit”,
endWord = “cog”,
wordList = [“hot”,“dot”,“dog”,“lot”,“log”,“cog”]

输出:
[
[“hit”,“hot”,“dot”,“dog”,“cog”],
[“hit”,“hot”,“lot”,“log”,“cog”]
]
示例 2:

输入:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,“dot”,“dog”,“lot”,“log”]

输出: []

解释: endWord “cog” 不在字典中,所以不存在符合要求的转换序列。

解法
把开始单词当作开始节点,结束单词当作结束节点,现在要解决的问题就是开始节点和结束节点是否有连接,求最短路径。

首先,如果结束单词一开始就不在单词列表里,可以直接返回,因为本身就不存在这样的节点。
其次,求最短路径,最容易想到的就是BFS解法,这个从起始节点开始遍历,如果找到了结束节点,代表找到了结果,同时这个肯定是最优解(路径最短)。
注意:此时我们并不能直接返回答案,因为要求的所有最短路径,所以我们要把这一层的所有满足结果都返回。

现在我们还有2个问题:

  • 如何通过某个节点找到它关联的节点,也就是遍历边,我们需要完成这个方法def edges(word),接收一个单词,返回它关联的所有单词
  • 遍历到了结束节点后,如何查到路径。

解决方案:

  • 直接根据题目意思来,每个字母都从a-z变换,如果在单词列表里,代表是有效节点,可以返回
  • 我们在BFS的时候实际上本来就已经知道了路径,所以完全可以把路径存在节点里面,你可以想象一下,你BFS的时候队列里是[word1,word2],原本word1,word2只存储了单词信息,那我可不可以把路径也存到节点里面,变成这样[[parentparent1, parent1,word1],[parentparent2, parent2,word2]],这样一来,我们既可以知道单词信息,同时也可以知道单词从头开始的遍历路径。

BFS的时间复杂度是O(n),n代表单词列表个数,总的时间复杂度是O(26nc)。和普通BFS也没啥区别,就是2个改动,1在节点里保存了路径,2你要写一个能遍历下个节点的方法

class Solution:
    def findLadders(self, beginWord, endWord, wordList):
        se=set(wordList)
        if not endWord in se:
            return []
        def edges(word):
            arr=list(word)
            for i in range(len(arr)):
                c=arr[i]
                for j in range(97,123):
                    arr[i]=chr(j)
                    newWord=''.join(arr)
                    if newWord in se and not newWord in marked:
                        yield newWord
                arr[i]=c
        res=[]
        marked=set()
        queue=[[beginWord]]
        while queue:
            temp=[]
            found=False
            for words in queue:
                marked.add(words[-1])
            for words in queue:
                for w in edges(words[-1]):
                    v=words+[w]
                    if w == endWord:
                        res.append(v)
                        found=True
                    temp.append(v)
            if found:          #找到就不再遍历了,即使再有endWord,路径也会更长
                break
            queue=temp
        return res

优化一:
我们可以把每个单词看作可以hash成c个值,c代表单词长度,比如单词’hit’,可以hash成’it’,'ht’,'hi*'这3个值,如果2个单词的hash值有重合,说明这2个单词是可以通过改1个字母变成另外一个的,所以我们的寻找边方法def edges(word)可以优化成这样,此方法将寻找边的O(26*c)优化成了O©

    hash=collections.defaultdict(list)
    for word in wordList:
        for i in range(len(word)):
            hash[word[:i]+"*"+word[i+1:]].append(word)        #初始化:将单词列表的所有元素全部hash保存进字典
    def edges(word):
        for i in range(len(word)):
            for newWord in hash[word[:i]+'*'+word[i+1:]]:     #遍历该单词的所有hash,从字典里取出关联的单词
                if not newWord in marked:
                    yield newWord

优化二:双向BFS
因为已经知道了开始节点和结束节点,所以可以从2头开始遍历,哪头的节点少,就遍历哪头,如果2头的节点重合了(有交集)说明连上了,遍历结束。
如果一直没交集,任何一头走到末尾就可以结束,代表全部节点都遍历完成了。
这里为了更好的取交集,begin和end都用了set来表示,同时把路径单独存储在了path字典中,一旦出现交集,从最后一个节点,依据path的存储,直到找到开始节点,能出现交集,说明一定能找到开头。

class Solution:
    def findLadders(self, beginWord, endWord, wordList):
        if not endWord in wordList:
            return []
        hash=collections.defaultdict(list)
        for word in wordList:
            for i in range(len(word)):
                hash[word[:i]+"*"+word[i+1:]].append(word)
        def edges(word):
            for i in range(len(word)):
                for newWord in hash[word[:i]+'*'+word[i+1:]]:
                    if not newWord in marked:
                        yield newWord
        def findPath(end):
            res=[]
            for curr in end:
                for parent in path[curr[0]]:
                    res.append([parent]+curr)
            return res
        marked=set()
        path=collections.defaultdict(set)
        begin=set([beginWord])
        end=set([endWord])
        forward=True
        while begin and end:
            if len(begin)>len(end):
                begin,end=end,begin
                forward=not forward
            temp=set()
            for word in begin:
                marked.add(word)
            for word in begin:
                for w in edges(word):
                    temp.add(w)
                    if forward:
                        path[w].add(word)
                    else:
                        path[word].add(w)
            begin=temp
            if begin&end:
                res=[[endWord]]
                while res[0][0]!=beginWord:
                    res=findPath(res)
                return res
        return []

岛屿数量

给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。

示例 1:

输入:
11110
11010
11000
00000

输出: 1
示例 2:

输入:
11000
11000
00100
00011

输出: 3

解法
标准的回溯问题:遍历整个矩阵,找到为1的就上下左右遍历,并把遍历过的设置为0,因为不会再遍历第二次(省掉visited数组).
回溯其实还可以通过队列的防止保存下来,用迭代解决。

class Solution(object):
    def numIslands(self, grid):
        """
        :type grid: List[List[str]]
        :rtype: int
        """
        if not grid:
            return 0
        
        res = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '1':
                    res += 1
                    self.dfs(grid, i, j)
                    
        return res
    
    def dfs(self, grid, x, y):
        grid[x][y] = '0'
            
        if x > 0 and grid[x-1][y] == '1':
            self.dfs(grid, x-1, y)
            
        if x < len(grid) - 1 and grid[x+1][y] == '1':
            self.dfs(grid, x+1, y)
            
        if y > 0 and grid[x][y-1] == '1':
            self.dfs(grid, x, y-1)
            
        if y < len(grid[0]) - 1 and grid[x][y+1] == '1':
            self.dfs(grid, x, y+1)
                    

课程表

现在你总共有 n 门课需要选,记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?

示例 1:

输入: 2, [[1,0]]
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
示例 2:

输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。
说明:

输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
你可以假定输入的先决条件中没有重复的边。
提示:

这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
通过 DFS 进行拓扑排序 - 一个关于Coursera的精彩视频教程(21分钟),介绍拓扑排序的基本概念。
拓扑排序也可以通过 BFS 完成。

解法
拓扑排序:在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:

  1. 每个顶点出现且只出现一次。
  2. 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。
所以可以使用拓扑排序的方法,可以拓扑排序的结点数等于课程数,则表示可以完成拓扑排序,即可以完成课程。
另外一种方法:https://www.jianshu.com/p/4565fa200a62

class Solution(object):
    def canFinish(self, numCourses, prerequisites):
        """
        :type numCourses: int
        :type prerequisites: List[List[int]]
        :rtype: bool
        """
        if not prerequisites:
            return True
        
        in_degree = [0 for i in range(numCourses)]
        adj = [set() for i in range(numCourses)]
        
        for x, y in prerequisites:
            in_degree[x] += 1
            adj[y].add(x)
            
        q = []
        for x in range(numCourses):
            if in_degree[x] == 0:
                q.append(x)
                
        res = 0
        while q:
            tmp = q.pop(0)
            res += 1
            
            for x in adj[tmp]:
                in_degree[x] -= 1
                if in_degree[x] == 0:
                    q.append(x)
                    
        return res == numCourses
            
             

图的最短路径

有一张picture,n个岛屿(0 ~ n-1),岛与岛之前有的有桥相连,
怎么去表示它 (n=10万);输入x,y两个岛的编号,是否可以从x岛到达y岛;怎么走?最省事的一种走法

解法
表示图的方式:邻接矩阵、邻接表、一维数组表示二维数组、链表?直接存储边?
最短路径的求法:BFS或者DFS

# 找到一条从start到end的路径
def findPath(graph,start,end,path=[]):   
    path = path + [start]
    if start == end:
        return path 
    for node in graph[start]:
        if node not in path:
            newpath = findPath(graph,node,end,path)
            if newpath:
                return newpath
    return None
 
# 找到所有从start到end的路径
def findAllPath(graph,start,end,path=[]):
    path = path +[start]
    if start == end:
        return [path]
 
    paths = [] #存储所有路径    
    for node in graph[start]:
        if node not in path:
            newpaths = findAllPath(graph,node,end,path) 
            for newpath in newpaths:
                paths.append(newpath)
    return paths
 
# 查找最短路径
def findShortestPath(graph,start,end,path=[]):
    path = path +[start]
    if start == end:
        return path
    
    shortestPath = []
    for node in graph[start]:
        if node not in path:
            newpath = findShortestPath(graph,node,end,path)
            if newpath:
                if not shortestPath or len(newpath)<len(shortestPath):
                    shortestPath = newpath
    return shortestPath
 
    '''
主程序
'''
graph = {'A': ['B', 'C','D'],
         'B': [ 'E'],
         'C': ['D','F'],
         'D': ['B','E','G'],
         'E': [],
         'F': ['D','G'],
         'G': ['E']}
 
onepath = findPath(graph,'A','G')
print('一条路径:',onepath)
 
allpath = findAllPath(graph,'A','G')
print('\n所有路径:',allpath)
 
shortpath = findShortestPath(graph,'A','G')
print('\n最短路径:',shortpath)

BFS:

def f3(b, x, y):
    q = []
    for i in b[x]:
        q.append([i])
       
    res = []
    while q:
        path = q.pop(0)
        node = path[-1]
        if node == y:
            return True, path
        for node in range(b[node]):
            newpath = path
            newpath.append(node)
            q.append(newpath)
            
    return False, None

你可能感兴趣的:(leetcode,Python,leetcode,python,堆栈队列)