DFS-中等

中等题

1、面试题 04.05. 合法二叉搜索树 https://leetcode-cn.com/problems/legal-binary-search-tree-lcci/

考点:

1、按题意思是要比较当前节点和左右子树的值,也就是先要计算出左右子树的列表,才可知当前节点是否满足要求。 由此可知为后序遍历

2、(技巧)每次节点比较时,只要和左右子树的集合比较即可,所以迭代要返回当前节点子树的结合

3、(注意)需要考虑到 左右子树为空,左子树为空,右子树为空,左右子树均不为空等情况

class Solution:
    def helper(self, root, result):
        if root == None:
            return []

        l_list = self.helper(root.left, result)
        r_list = self.helper(root.right, result)
        print(str(root.val) + "*" + str(l_list) + "*" + str(r_list))
        if l_list == [] and r_list == []:
            return [root.val]
        elif l_list == [] and r_list and root.val < min(r_list):
            r_list.append(root.val)
            return r_list
        elif r_list == [] and l_list and  root.val > max(l_list):
            l_list.append(root.val)
            return l_list
        elif ((l_list and root.val > max(l_list)) and (r_list and root.val < min(r_list))):
            l_list.extend(r_list)
            l_list.append(root.val)
            return l_list
        else:
            result.append(False)

    def isValidBST(self, root: TreeNode) -> bool:
        result = []
        self.helper(root, result)
        return False if False in result else True

2、面试题 16.19. 水域大小 https://leetcode-cn.com/problems/pond-sizes-lcci/

考点:

1、典型的二维数组 深度搜索问题,根据找到的某个位置,深度搜索满足条件的块

class Solution:
    def helper(self, pos, now_pos, land):
        row = len(land)
        col = len(land[0])
        i, j = pos

        for x, y in [(i+1, j), (i-1, j), (i, j+1), (i, j-1), (i-1, j-1), (i+1, j-1), (i-1, j+1), (i+1, j+1)]:
            if 0 <= x < row and 0 <= y < col and land[x][y] == 0 and (x, y) not in now_pos:
                now_pos.append((x, y))
                self.helper((x, y), now_pos, land)
        return now_pos




    def pondSizes(self, land: List[List[int]]) -> List[int]:
        row = len(land)
        col = len(land[0])

        haved = []
        result = []
        for i in range(row):
            for j in range(col):
                if land[i][j] == 0 and (i, j) not in haved:
                    now_pos = []
                    now_pos.append((i, j))
                    self.helper((i, j), now_pos, land)
                    result.append(len(now_pos))
                    haved.extend(now_pos)
        result.sort()
        return result

3、面试题 04.06. 后继者 https://leetcode-cn.com/problems/successor-lcci/

考点

1、二叉树的中序遍历

2、(注意) 递归方法中设中间值和判断逻辑都在中序位置, 开始只做叶子节点返回值处理

class Solution:
    def helper(self, root, p, find, result):
        if root == None:
            return
        
        self.helper(root.left, p, find, result)
        
        if True in find and root is not None and result is not None:
            result.append(root)
        if root == p:
            find.append(True)
        self.helper(root.right, p, find, result)


    def inorderSuccessor(self, root: TreeNode, p: TreeNode) -> TreeNode:
        find = []
        result = []
        self.helper(root, p, find, result)
        return result[0] if result else None

4、面试题 17.22. 单词转换

考点:

1、深度搜索

按照常规的深度搜索+优先搜索 往目的字符串转的方式,还是会时间超时

class Solution:
    def helper(self, beginWord, endWord, result, wordList, cur_list):
        if len(result) > 0:
            return 

        if cur_list[-1] == endWord:
            result.append(cur_list)
            return
        
        word_len = len(beginWord)
        choice_words = []
        for i in range(word_len):
            if not beginWord[i] == endWord[i]:
                choice_words.append("%s%s%s" % (beginWord[0:i], endWord[i:i+1], beginWord[i+1:]))
        # print(beginWord + "&" + str(choice_words))
        for choice_word in choice_words:
            if choice_word in wordList:
                new_wordList = list(wordList)
                new_wordList.remove(choice_word)
                new_cur_list = list(cur_list)
                new_cur_list.append(choice_word)
                self.helper(choice_word, endWord, result, new_wordList, new_cur_list)
        
        # print(beginWord + "*" + str(wordList))
        for word in wordList:
            # 判断相差1
            bigger_diff = 0
            for i in range(word_len):
                if not beginWord[i] == word[i]:
                    bigger_diff += 1

            if bigger_diff == 1:
                
                new_wordList = list(wordList)
                new_wordList.remove(word)
                new_cur_list = list(cur_list)
                new_cur_list.append(word)
                # print(str(new_cur_list) + "**" + str(new_wordList))
                self.helper(word, endWord, result, new_wordList, new_cur_list)
            


    def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[str]:
        if endWord not in wordList:
            return []
        result = []
        cur_list = [beginWord]
        self.helper(beginWord, endWord, result, wordList, cur_list)
        # print(result)
        if len(result) > 0:
            return result[0]
        else:
            return []

 换个思路,引入字典: 使用*代表任意字符,构建字典如下:

 

第一列为key,后面列为value; 

按照startword 每个位置* 替换后开始深度搜索

 

考点:

1、数据结构,引入dict快速索引深度遍历元素

2、函数中函数,好处是 内部函数可以使用外部函数的参数; 注意内不函数要给外部函数参数赋值,需要将参数值定义为类变量 self.变量名

3、技巧:引入marked,已经访问过的单次没必要访问,因为只要访问过,该单词后面的路径计算就属于重复计算


class Solution:
    def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[str]:
        # 生成字典
        word_dict = collections.defaultdict(set)
        for word in wordList:
            for i in range(len(word)):
                word_dict[word[:i]+"*"+word[i+1:]].add(word)
        # print(word_dict)

        self.result = []
        def dfs(curWord, cur_list, marked):
            if curWord == endWord:
                # print(cur_list)
                self.result = cur_list
                return
            
            for i in range(len(curWord)):
                # print(curWord[:i]+"*"+curWord[i+1:] in word_dict)
                for word in word_dict[curWord[:i]+"*"+curWord[i+1:]]:
                    if word not in marked:
                        marked.append(word)
                        dfs(word, cur_list+[word], marked)
        
        dfs(beginWord, [beginWord], [beginWord])
        return self.result

  

5、面试题 04.12. 求和路径 https://leetcode-cn.com/problems/paths-with-sum-lcci/  最后差2个用例由于事件超时,以后研究

考点:

1、树的路径遍历,先序遍历(注意路径不同的path要复制list,防止互相影响)

2、list连续子列表等于某值,主要该题列表中的值可正可负,不能使用左右指针

class Solution:
    # 先序遍历所有的路径
    def helper(self, cur, cur_path, results):
        if cur == None:
            return
        
        if cur.left == None and cur.right == None:
            cur_path.append(cur)
            results.append(cur_path)
        else:
            cur_path.append(cur)
            l_cur_path = list(cur_path)
            self.helper(cur.left, l_cur_path, results)
            r_cur_path = list(cur_path)
            self.helper(cur.right, r_cur_path, results)

    def isum(self, ilist):
        iss = 0
        for i in range(len(ilist)):
            # print("***" + str(left) + "&"+str(right)+"&"+ str(i))
            iss += ilist[i].val
        # print(str(ilist) + "*"+str(iss))
        return iss

    def pathSum(self, root: TreeNode, sum: int) -> int:
        results = []
        cur_path = []
        
        self.helper(root, cur_path, results)
        # print(results)

        rresult = []
        
        # 满足条件的路径
        for result in results:
            # 双指针 统计满足 -- 当 整数值恒正,才会用到左右指针
            # le = len(result)
            # left = 0
            # for right in range(1, le+1):
            #     if self.isum(result[left:right]) == tt:
            #         rresult += 1
            #     while self.isum(result[left:right]) > tt and right > left + 1:
            #             left += 1
            #             if self.isum(result[left:right]) == tt:
            #                 rresult += 1

            # 由于值存在正负,直接用遍历
            for i in range(1, len(result)+1):
                for j in range(i):
                    if self.isum(result[j:i]) == tt:
                        if result[j:i] not in rresult:
                            rresult.append(result[j:i])
                
        return len(rresult)

6、剑指 Offer 12. 矩阵中的路径 https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/

考点:

1、深度搜索题,先找到满足第一个字符的位置,然后进行迭代的深度搜索

2、一定要主要 判断满足条件实在迭代函数的判断逻辑中,不是在迭代最开始的地方

class Solution:

    def helper(self, cur, have, next_pos, result, word, board):
        if True in result:
            return

        row = len(board)
        col = len(board[0])
        cur_i, cur_j = cur
        for i,j in [(cur_i - 1, cur_j),(cur_i, cur_j - 1),(cur_i+1, cur_j),(cur_i, cur_j+1)]:
            if 0 <= i < row and 0 <= j < col and (i, j) not in have:
                if board[i][j] == word[next_pos]:
                    if next_pos == len(word)-1:
                        result.append(True)
                        return
                    else:
                        new_have = list(have)
                        new_have.append((i, j))
                        self.helper((i, j), new_have, next_pos + 1, result, word, board)
        

    def exist(self, board: List[List[str]], word: str) -> bool:

        row = len(board)
        col = len(board[0])
        if not board:
            return False

        result = []
        for i in range(row):
            for j in range(col):
                if board[i][j] == word[0]:
                    if len(word) == 1:
                        return True
                    else:
                        have = set()
                        have.add((i, j))
                        self.helper((i, j), have, 1, result, word, board)
        if True in result:
            return True
        else:
            return False

7  1466. 重新规划路线 https://leetcode-cn.com/problems/reorder-routes-to-make-all-paths-lead-to-the-city-zero/

考点:

1、层次的深度搜索

2、基础层次深度搜索会时间超时;然后使用dict来加速

class Solution:
    change = 0
    # def helper(self, cur_list, rest_lists):
    #     if len(rest_lists) == 0:
    #         return 
        
        
    #     next_list = []
    #     for cur in cur_list:
    #         for rest_list in list(rest_lists):
    #             if cur in rest_list:
    #                 if cur == rest_list[0]:
    #                     self.change += 1
    #                     next_list.append(rest_list[1])
    #                 else:
    #                     next_list.append(rest_list[0])
    #                 rest_lists.remove(rest_list)
    #     self.helper(next_list, rest_lists)
    def helper(self, cur_list, connections_dict):
        if len(connections_dict) == 0:
            return
        
        next_list = []
        for cur in cur_list:
            all_lists = connections_dict.get(cur)
            for all_list in all_lists:
                if all_list[0] == cur:
                    self.change += 1
                    next_list.append(all_list[1])
                    connections_dict[all_list[1]].remove(all_list)
                else:
                    next_list.append(all_list[0])
                    connections_dict[all_list[0]].remove(all_list)
            del connections_dict[cur]
        self.helper(next_list, connections_dict)


    def minReorder(self, n: int, connections: List[List[int]]) -> int:

        connections_dict = dict()
        for i in range(len(connections)):
            connection = connections[i]
            if connections_dict.get(connection[0]):
                connections_dict.get(connection[0]).append(connection)
            else:
                connections_dict[connection[0]] = [connection]
            if connections_dict.get(connection[1]):
                connections_dict.get(connection[1]).append(connection)
            else:
                connections_dict[connection[1]] = [connection]

        # print(connections_dict)
        cur_list = [0]
        self.helper(cur_list, connections_dict)
        # self.helper(cur_list, connections)
        return self.change

8  971. 翻转二叉树以匹配先序遍历 https://leetcode-cn.com/problems/flip-binary-tree-to-match-preorder-traversal/

考点:

1、先序遍历

2、技巧,主要逻辑(判断值是否和list中相等,是否置换左右子树 )均放在先序 位置上

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

class Solution:
    Failed = False
    def helper(self, root, voyage, result):
        if self.Failed == True or root == None or voyage == None:
            return 
        
        if not root.val == voyage[0]:
            self.Failed = True
            return

        del voyage[0]
        if root.left and not root.left.val == voyage[0]:
            tmp = root.left
            root.left = root.right
            root.right = tmp
            result.append(root.val)
        self.helper(root.left, voyage, result)
        self.helper(root.right, voyage, result)
        

    def flipMatchVoyage(self, root: TreeNode, voyage: List[int]) -> List[int]:
        result = []
        if root == None:
            return [-1]
        self.helper(root, voyage, result)  

        if self.Failed:
            return [-1]
        else:
            return result      

面试题 17.07. 婴儿名字 https://leetcode-cn.com/problems/baby-names-lcci/

考点:

1、并查集,比常规并查集复杂的地方在 需要维护一个字典保存各元素位置

2、注意:在关系中的名字不一定在实际中存在,所有 按照实际有的名字建立 需要进行并查集的列表。 将要进行并查集建立关系的元素,需要2个元素都在并查集列表中,才需要进行。要不然没有必要按照并查集建立关系

class Solution:
    def search(self, name, name_list, name_index):
        if name_list[name_index.get(name)] != name:
            name_list[name_index.get(name)] = self.search(name_list[name_index.get(name)], name_list, name_index)
        return name_list[name_index.get(name)]

    def union(self, name1, name2, name_list, name_index):
        if name1 in name_index  and name2 in name_index:
            
            name1_first = self.search(name1, name_list, name_index)
            name2_first = self.search(name2, name_list, name_index)
            # print("&&"+ str(name1_first) + str(name2_first))
            if name1_first > name2_first:
                name_list[name_index.get(name1_first)] = name2_first
            else:
                name_list[name_index.get(name2_first)] = name1_first

    def trulyMostPopular(self, names: List[str], synonyms: List[str]) -> List[str]:
        name_num = dict()
        name_index = dict()
        index = 0
        name_list = []
        for name in names:
            iname = name[:name.index("(")]
            num = int(name[name.index("(") + 1: -1])
            name_num[iname] = num
            name_index[iname] = index
            index = index + 1
            name_list.append(iname)
        # print(name_num)
        # print(name_index)
        # print(name_list)

        # 并查集,找到相同真实名字
        for syno in synonyms:
            sname_list = syno[1:-1].split(",")
            name1 = sname_list[0]
            name2 = sname_list[1]
            self.union(name1, name2, name_list, name_index)
        # print("*"+str(name_list))
        for iname in name_index.keys():
            self.search(iname, name_list, name_index)

        # print(name_list)

        # 计算总个数
        result = {}
        for name, num in name_num.items():
            if result.get(name_list[name_index.get(name)]):
                result[name_list[name_index.get(name)]] = result.get(name_list[name_index.get(name)]) + num
            else:
                result[name_list[name_index.get(name)]] = num
        # 拼接成返回格式
        result_list = []
        for name, num in result.items():
            result_list.append(name + "(%d)" % num)
        
        return result_list

947. 移除最多的同行或同列石头 

https://leetcode-cn.com/problems/most-stones-removed-with-same-row-or-column/submissions/

 

10 721. 账户合并 https://leetcode-cn.com/problems/accounts-merge/

考点:

1、一看该题,考查的比较像并查集,但是 注意里面 长度要求不高,直接使用递归暴力求解方法

2、注意,既然该题考查是并查集,也就是merge_list 方法一遍遍历不出来结果,所以该出使用递归

class Solution:

    def merge_list(self, mails_list):
        change = False
        all_result = []
        for mails in list(mails_list):
            is_find = False
            for mail in mails:
                for idx in range(len(all_result)):
                    if mail in all_result[idx]:
                        all_result[idx].extend(mails)
                        all_result[idx] = list(set(all_result[idx]))
                        is_find = True
                        change = True
                        break
                if is_find:
                    continue
            if is_find:
                continue
            else:
                all_result.append(mails)
        if change:
            return self.in_list(all_result)
        else:
            return mails_list

    def add_result(self, name, all_list, results):
        for s_list in all_list:
            result = [name]
            tmp = list(set(s_list))
            tmp.sort()
            result.extend(tmp)
            results.append(result)

    def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]:
        # 按照account 计算map
        accounts_dict = collections.defaultdict(list)
        for account in accounts:
            name = account[0]
            accounts_dict[name].append(account[1:])   

        results = []
        for name, mails_list in accounts_dict.items():
            all_list = self.merge_list(mails_list)
            self.add_result(name, all_list, results)

        return results          

11. 1026. 节点与其祖先之间的最大差值 https://leetcode-cn.com/problems/maximum-difference-between-node-and-ancestor/

考点:

1、深度搜索,之前有道题目是 计算每个节点及子节点最大,最小值,可以很快得到 最大距离

class Solution:
    def set_max(self, a, b):
        if a - b > self.max_dis:
            self.max_dis = a - b
        elif b - a > self.max_dis:
            self.max_dis = b - a

    def helper(self, root):
        if root == None:
            return None, None
        
        l_min, l_max = self.helper(root.left)
        r_min, r_max = self.helper(root.right)

        # 左右节点都为空
        if l_min==None and r_min==None:
            return root.val, root.val
        # 左节点为空
        elif l_min==None:
            self.set_max(r_min, root.val)
            self.set_max(r_max, root.val)
            return min(r_min, root.val), max(r_max, root.val)
        elif r_min==None:
            self.set_max(l_min, root.val)
            self.set_max(l_max, root.val)
            return min(l_min, root.val), max(l_max, root.val)
        else:
            self.set_max(r_min, root.val)
            self.set_max(r_max, root.val)
            self.set_max(l_min, root.val)
            self.set_max(l_max, root.val)
            return min(min(l_min, r_min), root.val), max(max(l_max, r_max), root.val)


    def maxAncestorDiff(self, root: TreeNode) -> int:
        # 使用后续遍历,计算每个节点最小值和最大值
        self.max_dis = 0
        self.helper(root)
        return self.max_dis

12  1376. 通知所有员工所需的时间  https://leetcode-cn.com/problems/time-needed-to-inform-all-employees/

考点:

1、深度搜索

直接按照深度搜索 时间超时

class Solution:
    def helper(self, n, cur, manager, informTime, cur_time):

        not_end = False
        # 找到下一层
        for i in range(n):
            if manager[i] == cur:
                self.helper(n, i, manager, informTime, cur_time+informTime[cur]) 
                not_end = True
        if not not_end:
            if self.all_time < cur_time:
                self.all_time = cur_time


    def numOfMinutes(self, n: int, headID: int, manager: List[int], informTime: List[int]) -> int:
        cur_list = [headID]
        self.all_time = 0
        self.helper(n, headID, manager, informTime, 0)
        return self.all_time

 引入dict表示这种2层关系,通过

class Solution:
    def helper(self, n, cur, manager_dict, informTime, cur_time):

        if manager_dict[cur]:
            for chre in manager_dict[cur]:
                self.helper(n, chre, manager_dict, informTime, cur_time+informTime[cur]) 
                not_end = True
        else:
            if self.all_time < cur_time:
                self.all_time = cur_time


    def numOfMinutes(self, n: int, headID: int, manager: List[int], informTime: List[int]) -> int:

        # 管理者关系字典
        manager_dict = collections.defaultdict(list)
        for i in range(n):
            if not manager[i] == -1:
                manager_dict[manager[i]].append(i)

        cur_list = [headID]
        self.all_time = 0
        self.helper(n, headID, manager_dict, informTime, 0)
        return self.all_time

13 638. 大礼包  https://leetcode-cn.com/problems/shopping-offers/

考点:

1。深度搜索, 假设 套餐肯定比单独购买便宜,所以优先遍历递归套餐,然后遍历递归单个商品

超出时间限制

class Solution:
    def helper(self, price, special, needs, cur_price):
        if needs == [0] * len(needs):
            if cur_price < self.total_price:
                self.total_price = cur_price
                return
        
        for i in range(len(special)):
            tmp_need = list(needs)
            fil_condition = True
            for j in range(len(needs)):
                new_j = tmp_need[j] - special[i][j]
                if new_j >= 0:
                    tmp_need[j] = tmp_need[j] - special[i][j]
                else:
                    fil_condition = False
                    break
            if fil_condition:
                # print(tmp_need, cur_price + special[i][-1])
                self.helper(price, special, tmp_need, cur_price + special[i][-1])
        
        for i in range(len(needs)):
            tmp_need = list(needs)
            if tmp_need[i] > 0:
                tmp = tmp_need[i]
                tmp_need[i] = 0
                self.helper(price, special, tmp_need, cur_price + tmp * price[i])

    def shoppingOffers(self, price: List[int], special: List[List[int]], needs: List[int]) -> int:
        self.total_price = 0
        for i in range(len(needs)):
            self.total_price += needs[i] * price[i]
        self.helper(price, special, needs, 0)
        return self.total_price

 考虑加速递归过程,考虑2个方面优化后通过: 1、 已经知道不满足要求的套餐,删除,下次不用再递归   2、单独购买商品,套餐参数设置为空数组,均不用 递归遍历套餐了(基于套餐肯定比单独购买便宜,前面已经穷尽套餐了,所有参数传递的套餐设置为空数组)

class Solution:
    def helper(self, price, special, needs, cur_price):
        if needs == [0] * len(needs):
            if cur_price < self.total_price:
                self.total_price = cur_price
                return
        
        # 选择可遍历的list
    

        for i in range(len(special)):
            tmp_need = list(needs)
            special_new = list(special)
            fil_condition = True
            for j in range(len(needs)):
                new_j = tmp_need[j] - special[i][j]
                if new_j >= 0:
                    tmp_need[j] = tmp_need[j] - special[i][j]
                else:
                    fil_condition = False
                    special_new.remove(special[i])
                    break
            if fil_condition:
                self.helper(price, special_new, tmp_need, cur_price + special[i][-1])
        
        for i in range(len(needs)):
            tmp_need = list(needs)
            if tmp_need[i] > 0:
                tmp = tmp_need[i]
                tmp_need[i] = 0
                self.helper(price, [], tmp_need, cur_price + tmp * price[i])

    def shoppingOffers(self, price: List[int], special: List[List[int]], needs: List[int]) -> int:
        self.total_price = 0
        for i in range(len(needs)):
            self.total_price += needs[i] * price[i]
        self.helper(price, special, needs, 0)
        return self.total_price

14   1457. 二叉树中的伪回文路径 https://leetcode-cn.com/problems/pseudo-palindromic-paths-in-a-binary-tree/

考点:

1、 二叉树中 路径个数,使用深度搜索

2、 判断数列是否构成回文窜,判断逻辑是 如果字符出现奇数次的 个数大于等于1,则不能构成回文。 <=1 则可以构成回文

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def helper(self, root, cur_path):
        if root == None:
            return

        if root.left == None and root.right == None:
            tmp_path = list(cur_path)
            tmp_path.append(root.val)
            self.paths.append(tmp_path)
            return
        
        tmp_path = list(cur_path)
        tmp_path.append(root.val)
        self.helper(root.left, tmp_path)
        self.helper(root.right, tmp_path)

    def pseudoPalindromicPaths (self, root: TreeNode) -> int:
        self.paths = []
        # 1.找到所有路径
        self.helper(root, [])        
        # print(self.paths)
        # 2.数列是否是回文(判断字符出现奇数次是否大于1,此处引入字典)
        result = 0
        for path in self.paths:
            result_dict = dict()
            for item in path:
                if result_dict.get(item):
                    result_dict[item] = result_dict.get(item) + 1
                else:
                    result_dict[item] = 1
            ji_count = 0
            for key, value in result_dict.items():
                if not value % 2 == 0:
                    ji_count += 1
            if ji_count <= 1:
                result += 1
        return result
posted @ 2020-10-23 00:25  哈哈哈喽喽喽  阅读(67)  评论(0编辑  收藏  举报