刷题记录10---买卖股票的最佳时机+合并区间+岛屿的数量+课程表+实现Trie(前缀树)

前言

所有题目均来自力扣题库中的hot 100,之所以要记录在这里,只是方便后续复习

121.买卖股票的最佳时机

题目:
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
解题思路:
【遍历列表】如果赚更多的钱,肯定是要在最低点买入,在而后日子里的最大高点卖出,所以我们要获取到最低点,我们可以遍历数组,在这个过程中要做两件事,第一个是更新最低点,第二个是根据今天价格和最低点价格计算卖出的利润且保留出最大值,遍历完成,所保留的最大值就是这个过程中最大利润
代码(python):

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        min_price = sys.maxsize
        max_money = 0
        for price in prices:
            if price < min_price:
                min_price = price
            else:
                max_money = max((price - min_price), max_money)
        return max_money

代码(java):

class Solution {
    public int maxProfit(int[] prices) {
        // 定义最低价格 ,随着遍历会更新
        int minPrice = Integer.MAX_VALUE;
        // 定义结果值,随着遍历会更新
        int result = 0;
        //遍历数组
        for(int i = 0; i < prices.length; i ++){
            // 如果今天的价格小于 最低价格, 则更新价格,注意今天只能更新价格,不能卖股票
            if(prices[i] < minPrice){
                minPrice = prices[i];
            // 如果今天的价格不小于最低价格,计算一下今天卖会赚多少并和结果值比较,取较大值赋给结果值
            }else{
                result = Math.max(result, prices[i] - minPrice);
            }
        }

        return result;
    }
}

知识点:

原题链接:买卖股票的最佳时机

56.合并区间

题目:
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
解题思路:
【排序】我们可以将二维数组排序,而排序的方式是按每个数组的第一个元素从小到达排序,这样得到的新二维数组就不会存在后面的数组的头还在前面的头之前了;此时我们判断两个数组是否可以合并,就看第二个数组的头是否小于等于第一个数组的尾,若是则可合并,即更新前一个数组的尾就好了,若不是则将二个数组直接添加到结果集合中
代码(python):

class Solution(object):
    def merge(self, intervals):
        """
        :type intervals: List[List[int]]
        :rtype: List[List[int]]
        """

        intervals.sort(key=lambda x : x[0])

        result = []
        last = 0

        for interval in intervals:
            if len(result) == 0:
                result.append(interval)
            else:
                last = result[-1][-1]
                if interval[0] > last:
                    result.append(interval)
                else:
                    result[-1][-1] = max(last, interval[-1])
        
        return result

代码(java):

class Solution {
    public int[][] merge(int[][] intervals) {
        // 将二维数组排序,按照每个数组的第一个元素从小到大排序
        Arrays.sort(intervals, new Comparator<int[]>(){
            public int compare(int[] interval1, int[] interval2){
                return interval1[0] - interval2[0];
            }
        });
        // 定义列表存储合并后的数组
        ArrayList<int[]> result = new ArrayList<>();
        // 遍历得到的新二维数组
        for (int i = 0; i < intervals.length; i ++){
            int len = result.size();
            // 如果列表中还没有数组 或者 当前数组的头 大于 列表中前一个数组的尾 ,则说明这两个数组不相交,将当前数组赋值添加到列表中
            if (len == 0  || result.get(len - 1)[1] < intervals[i][0]){
                result.add(new int[]{intervals[i][0], intervals[i][1]});
            // 若当前数组的头小于等于列表中前一个数组的尾,说明这两个数组相交,则更新前一个数组的尾即可,更新为两个数组的尾的较大值
            }else{
                result.get(len - 1)[1] = Math.max(result.get(len - 1)[1], intervals[i][1]);
            }
        }
        // 最后将列表转化为二维数组返回
        return result.toArray(new int[result.size()][2]);
    }
}

知识点:

  • python中调用列表排序方法时可以通过传递key值的方式定义比较值:list.sort(key=lambda x : x[0]),其中采用了lambda表达式:x为list中每个元素,而x[0]则代表key是x的第一个元素
  • java中调用系统排序方法时可以通过传入比较器的方式定义排序规则,其中比较器是一个具体的Comparator对象,要重写其campare方式进行排序,详情见代码
  • java中将列表转化为数组,可以用list.toArray(new in[])方法,其中传入的是用于存储元素的数组

原题连接:合并区间

200.岛屿的数量

题目:
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [
[“1”,“1”,“1”,“1”,“0”],
[“1”,“1”,“0”,“1”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“0”,“0”,“0”]
]
输出:1
示例 2:
输入:grid = [
[“1”,“1”,“0”,“0”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“1”,“0”,“0”],
[“0”,“0”,“0”,“1”,“1”]
]
输出:3
解题思路:
【深度优先搜索】我们可以遍历整个二维网格,如果遇到了1就计数,由于相邻的1不能重复计数,所以当我们遇到1时,就向四周扩散,如果四周还有1,将其变成0,在向四周扩展,直至到了边界或者遇到了0,这样就能将以该位置为起点的所有相邻的1变成0,达到不重复计数的目的,因为每次扩散都是做相同操作,1置为0和继续扩散,只不过位置不同罢了,就可以考虑用递归,把位置当作参数传入递归函数,中止条件是当前值不是1则直接返回。遍历完成后,返回计数界结果值即可
代码(python):

class Solution(object):
    def numIslands(self, grid):
        """
        :type grid: List[List[str]]
        :rtype: int
        """
        # 定义结果值
        result = 0
        # 每个位置进行判断,如果当前是1,就结果值+1 并且以该位置为起点,向四周扩散寻找相邻的1并变成0,这样就不会重复计算
        for i in range(0, len(grid)):
            for j in range(0, len(grid[0])):
                if grid[i][j] == "1":
                    result += 1
                    self.dfs(grid, i, j)
        return result

    def dfs(self, grid, i, j):
        # 如果当前值不是1 则直接返回
        if grid[i][j] != "1":
            return
        # 将当前值置为0
        grid[i][j] = "0"
        # 并通过递归的方式,将四周的1置为0 注意要判断是否处于边界位置
        if i > 0:
            self.dfs(grid, i - 1, j)
        if i < len(grid) - 1:
            self.dfs(grid, i + 1, j)
        if j > 0:
            self.dfs(grid, i, j - 1)
        if j < len(grid[0]) - 1:
            self.dfs(grid, i, j + 1)

代码(java):

class Solution {
    public int numIslands(char[][] grid) {

        int lands = 0;
        for (int i = 0; i < grid.length; i++){
            for (int j = 0; j < grid[0].length; j++){
                if (grid[i][j] == '1'){
                    lands ++;
                    dfs(grid, i, j);
                }
            }
        }
        return lands;
    }

    private void dfs(char[][] grid, int i , int j){
        if(grid[i][j] == '0'){
            return;
        }

        grid[i][j] = '0';

        if(i > 0 && grid[i - 1][j] == '1'){
            dfs(grid, i - 1, j);
        }
        if(j > 0 && grid[i][j - 1] == '1'){
            dfs(grid,i, j - 1);
        }
        if(i < grid.length - 1 && grid[i + 1][j] == '1'){
            dfs(grid, i + 1, j);
        }
        if(j < grid[0].length - 1 && grid[i][j + 1] == '1'){
            dfs(grid, i, j + 1);
        }
    }
}

知识点:

  • 深度优先搜索就像二叉树的前中后序遍历,先把一条路走完再走其他路;广度优先遍历就是二叉树的层序遍历,先把这一层的几次走完再走下一层

原题链接:岛屿数量

207.课程表

题目:
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
解题思路:
【深度优先搜索】我们可以遍历科目,每次遍历做相同的事情,就是搜索当前科目是否有依赖关系,如果有依赖关系,搜索依赖的科目是否有依赖关系,就这样一层一层搜索,直到最终没了依赖关系或者其依赖关系是刚刚找这条路径中的某一门科目,说白了类似于由依赖关系拼成的一个链表是否有环,如果是找到了尽头结束的搜索该门课就可以完成,如果是发现了环结束的搜索,则该门课就不能完成;那我们怎么确定哪些课程处于搜索过程中也就是处于这个虚拟链表中,可以考虑定义一个数组,从0到i每个位置对应着i课程的状态,0代表没有被搜索过的课,1代表搜索中的课,2代表完成搜索的课,这样在每次遍历前判断一下该课程状态是否0,若为0才进行搜索,另外在搜索每个课程的依赖关系时,我们可以将该课程状态置为1表示搜索中,如果后续的搜索又回到了这个被置为1的课程,就说明成了环,最后某个科目如果搜索完成我们可以将其状态置为2表示搜索过,这样在后续的搜索中遇到2直接返回即可,因为已经搜索过了
代码(python):

class Solution(object):

    # 需要注意的是这两个值要在方法里重新初始化,因为ide调用关系,如果未在方法中重新初始化,各个测试用例之间可能会有影响
    map = None
    result = True
    status = None

    def canFinish(self, numCourses, prerequisites):
        """
        :type numCourses: int
        :type prerequisites: List[List[int]]
        :rtype: bool
        """
        # 定义状态数组,用来维护科目的状态,0代表为搜索过, 1 代表搜索中,2代表搜索完成
        self.status = [0] * numCourses
        # 定义字典,用来存储依赖关系,由于某个科目不一定只依赖一门科目,所以值要用list集合
        self.map = collections.defaultdict(list)
        # 定义结果值
        self.result = True
        # 初始化依赖关系
        for pre in prerequisites:
            self.map[pre[0]].append(pre[1])
        # 遍历每个科目
        for i in range(0, numCourses):
            # 如果当前科目处于未搜索过状态
            if self.status[i] == 0:
                # 进行深度优先搜索
                self.dfs(i)
                # 如果搜索完成发现结果被置为false,直接返回false即可
                if not self.result:
                    return False
        #如果遍历完还没返回false,结果就是true
        return self.result

    def dfs(self, i):
        # 如果当前科目处于搜索中状态 说明以及成了环,结果值置为false并返回
        if self.status[i] == 1:
            self.result = False
            return
        # 如果当前科目处于搜索完成状态,并且到现在程序还没返回,说明该路经没环,直接返回即可
        if self.status[i] == 2:
            return
        # 将当前值置为搜索状态
        self.status[i] = 1
        # 获取当前值的依赖置,并依次递归搜索各个依赖值,每次判断一下结果值是否已经置为false,若为直接返回即可
        if self.map.has_key(i):
            for other in self.map[i]:
                self.dfs(other)
                if not self.result:
                    return
        # 各个依赖值均递归完成且未返回,说明没环,将该科门状态置为已完成
        self.status[i] = 2
        

代码(java):

class Solution {

    public boolean res = true;
    public List<List<Integer>> bians = new ArrayList<>();
    public int[] status;

    public boolean canFinish(int numCourses, int[][] prerequisites) {

        status = new int[numCourses];
        for(int i = 0; i < numCourses; i++){
            bians.add(new ArrayList<Integer>());
        }

        for(int[] bian : prerequisites){
            bians.get(bian[1]).add(bian[0]);
        }

        for(int i = 0; i < numCourses && res; i++){
            if(status[i] == 0){
                dfs(i);
            }
        }

        return res;

    }

    public void dfs(int i){
        status[i] = 1;

        for(int next : bians.get(i)){
            if(status[next] == 0){
                dfs(next);
                if(res == false){
                    return;
                }
            }
            if(status[next] == 1){
                res = false;
                return;
            }
        }

        status[i] = 2;
    }
}

知识点:

  • 类的公共变量最好在方法里重新初始化,因为ide调用关系,如果未在方法中重新初始化,各个测试用例之间可能会有影响

原题链接:课程表

208.实现Trie(前缀树)

题目:
Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
请你实现 Trie 类:
Trie() 初始化前缀树对象。
void insert(String word) 向前缀树中插入字符串 word 。
boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
示例:
输入
[“Trie”, “insert”, “search”, “search”, “startsWith”, “insert”, “search”]
[[], [“apple”], [“apple”], [“app”], [“app”], [“app”], [“app”]]
输出
[null, null, true, false, true, null, true]
解释
Trie trie = new Trie();
trie.insert(“apple”);
trie.search(“apple”); // 返回 True
trie.search(“app”); // 返回 False
trie.startsWith(“app”); // 返回 True
trie.insert(“app”);
trie.search(“app”); // 返回 True
解题思路:
【前缀树】大致思路是,将字符串拆分成每个字符,每个字符就是前缀树的每一层,每一层都有25个子节点分别代表26个字符,所以我们在设计节点结构是要有一个26长度的列表存储每个子节点,26个字符在列表中的位置可以根据和字符a相减得到的偏移量而定;在添加字符串时,我们从树的根节点开始,看字符串第1个字符在子节点列表中的对应位置是否有节点,若没有则新建节点(代表有该前缀),然后在下一层看字符串第2个字符,依次类推直到字符串所有字符遍历完成;查看是否存储某个字符串时和插入的思路差不多,从根节点开始向下一层一层检查字符串每个字符的位置是否有节点,如果某一层没有某个字符,说明没有该字符串,那如果每字符都完成了检查就一定有该字符串么?不一定,因为从上到下这个路径可能是其他字符串添加时创建的,只不过其他字符串包含了该字符串而已(apple和app关系),那怎么判断是该字符串添加的路径还是其他字符串添加的呢?我们可以在节点结构中添加一个标志位属性,代表是否有字符串已该节点为结束节点,该表示位默认是False,只有在添加字符串时,完成所有字符的遍历,在最后一层的对应字符节点中将其标志位置为True,这样在判断是否有该节点时在判断一下该标志位就得知答案了;至于判断是否有前缀,就是在判断是否有字符串的基础上少一个刚刚提到的标志位判断
代码(python):

class Trie(object):

    def __init__(self):
        # 定义列表 用来存储子节点,从0-25依次代表26的字符
        self.children = [None] * 26
        # 定义标志位 标志是否有以当前节点为结尾的字符串
        self.is_end = False

    def insert(self, word):
        """
        :type word: str
        :rtype: None
        """
        # 定义索引节点为根节点
        cur = self
        # 对于字符串的每个字符
        for i in range(0, len(word)):
            # 通过当前字符 和 'a' 相减计算出 该字符在子节点列表中的位置
            index = ord(word[i]) - ord('a')
            # 如果该位置还是空 则在该位置新建节点
            if cur.children[index] is None:
                cur.children[index] = Trie()
            # 索引节点等于该位置的节点 和遍历字符串的索引 同步 滑动,依次向下寻找每个字符
            cur = cur.children[index]
        # 最后当字符串遍历完成,在当前索引节点位置的 标志位 置为True,代表有字符串以当前节点结尾
        cur.is_end = True


    def search(self, word):
        """
        :type word: str
        :rtype: bool
        """
        # 定义索引节点为根节点
        cur = self
        # 对于字符串的每个字符
        for i in range(0, len(word)):
            # 通过当前字符 和 'a' 相减计算出 该字符在子节点列表中的位置
            index = ord(word[i]) - ord('a')
            # 如果该位置还是空 则直接返回False
            if cur.children[index] is None:
                return False
            # 索引节点等于该位置的节点 和遍历字符串的索引 同步 滑动,依次向下寻找每个字符
            cur = cur.children[index]
        # 字符串遍历完成后还没返回,说该路径存在,接下来就看该位置的几点的标志位是否为True,是则存在该字符串,不是则是其他字符串存储的路径
        return cur.is_end


    def startsWith(self, prefix):
        """
        :type prefix: str
        :rtype: bool
        """
        # 定义索引节点为根节点
        cur = self
        # 对于字符串的每个字符
        for i in range(0, len(prefix)):
             # 通过当前字符 和 'a' 相减计算出 该字符在子节点列表中的位置
            index = ord(prefix[i]) - ord('a')
            # 如果该位置还是空 则直接返回False
            if cur.children[index] is None:
                return False
             # 索引节点等于该位置的节点 和遍历字符串的索引 同步 滑动,依次向下寻找每个字符
            cur = cur.children[index]
        # 前缀遍历完还未返回False,说明有该路径即有前缀
        return True
        

代码(java):

class Trie {

    public boolean isEnd = false;
    public HashMap<Character, Trie> children = new HashMap<>();

    public Trie() {

    }
    
    public void insert(String word) {
        char[] chars = word.toCharArray();
        Trie node = this;
        for(int i = 0; i < chars.length; i++){
            if(node.children.keySet().contains(chars[i])){
                node = node.children.get(chars[i]);
            }else{
                Trie child = new Trie();
                node.children.put(chars[i], child);
                node = node.children.get(chars[i]);
            }
        }
        node.isEnd = true;
    }
    
    public boolean search(String word) {
        char[] chars = word.toCharArray();
        Trie node = this;
        for(int i = 0; i < chars.length; i++){
            if(node.children.keySet().contains(chars[i]) == false){
                return false;
            }else{
                node = node.children.get(chars[i]);
            }
        }
        return node.isEnd;
    }
    
    public boolean startsWith(String prefix) {
        char[] chars = prefix.toCharArray();
        Trie node = this;
        for(int i = 0; i < chars.length; i++){
            if(node.children.keySet().contains(chars[i]) == false){
                return false;
            }else{
                node = node.children.get(chars[i]);
            }
        }
        return true;
    }
}

知识点:

  • python中,如果要遍历字符串的每一个字符,可以通过for char in string,这样迭代拿到的每一个char默认就是字符类型,而不是字符串类型;java中可以用string.charAt(i)方法
  • python中要想计算字符之间相加减的值,需要先用ord(char)方法获取当前字符的值;而java中可以直接对两个字符串相加减
  • python中获取当前对象可用self;java中用this

原题链接:实现Tire(前缀树)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值