Leetcode面T17(11-20)树

本文介绍了如何高效地在大型文本中查找单词最短距离,以及如何在二叉搜索树中进行原址转换为单向链表。涉及的算法包括单词距离的动态规划和二叉树的递归遍历。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Q17.11 单词距离

有个内含单词的超大文本文件,给定任意两个单词,找出在这个文件中这两个单词的最短距离(相隔单词数)。如果寻找过程在这个文件中会重复多次,而每次寻找的单词不同,你能对此优化吗?

示例:

输入:words = ["I","am","a","student","from","a","university","in","a","city"], word1 = "a", word2 = "student"
输出:1
提示:

words.length <= 100000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-closest-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

import java.lang.*;
class Solution {
    public int findClosest(String[] words, String word1, String word2) {
        int start = -10000;
        int end = 10000;
        int min = 10000;
        for (int i = 0; i < words.length; i++) {
            if (words[i].equals(word1)) {
                start = i;
            }
            if (words[i].equals(word2)) {
                end = i;
            }
            min = Math.min(min, Math.abs(end-start));
        }
        return min;
    }
}

Q17.12 BiNode

二叉树数据结构TreeNode可用来表示单向链表(其中left置空,right为下一个链表节点)。实现一个方法,把二叉搜索树转换为单向链表,要求依然符合二叉搜索树的性质,转换操作应是原址的,也就是在原始的二叉搜索树上直接修改。

返回转换后的单向链表的头节点。

注意:本题相对原题稍作改动

 

示例:

输入: [4,2,5,1,3,null,6,0]
输出: [0,null,1,null,2,null,3,null,4,null,5,null,6]
提示:

节点数量不会超过 100000。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binode-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

简单的递归,借用一个先驱节点当做链表一样

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
     TreeNode f;

    public TreeNode convertBiNode(TreeNode root) {
        f = new TreeNode(Integer.MIN_VALUE);
        TreeNode pre = f;
        dfs(root);
        return pre.right;
    }
    public void dfs(TreeNode root) {
        if (root == null) return;
        dfs(root.left);
        f.right = root;
        root.left = null;
        f = f.right;
        dfs(root.right);
    }
}

Q17.13 恢复空格

哦,不!你不小心把一个长篇文章中的空格、标点都删掉了,并且大写也弄成了小写。像句子"I reset the computer. It still didn’t boot!"已经变成了"iresetthecomputeritstilldidntboot"。在处理标点符号和大小写之前,你得先把它断成词语。当然了,你有一本厚厚的词典dictionary,不过,有些词没在词典里。假设文章用sentence表示,设计一个算法,把文章断开,要求未识别的字符最少,返回未识别的字符数。

注意:本题相对原题稍作改动,只需返回未识别的字符数

 

示例:

输入:
dictionary = ["looked","just","like","her","brother"]
sentence = "jesslookedjustliketimherbrother"
输出: 7
解释: 断句后为"jess looked just like tim her brother",共7个未识别字符。
提示:

0 <= len(sentence) <= 1000
dictionary中总字符数不超过 150000。
你可以认为dictionary和sentence中只包含小写字母。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/re-space-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:直接求最少匹配的字符数比较复杂,所以反过来我们求能匹配的最大字符数。这道题相当于一个 完全0-1背包问题,背包容量为字符串长度,物品(字典中的单词)数量没有限制。我们要做的就是尽可能的填满这个背包。

1.定义状态: dp[i][j] 表示字符串(0~i),字典(0~i)的状态下,匹配的最大字符数。

2.状态转移:从背包末尾开始考虑:

当单词能塞进背包末尾的时候,即 sentence.substring(i-len,i) 与 当前单词相等,len 为单词长度。此时单词有两种状态,塞进背包,和不塞进背包,记住单词可以重复取用,取这两种状态的最大值:dp[i][j] = max(dp[i-len][j]+len,dp[i][j-1])。

当单词不能塞进背包末尾的时候,取字符串(0~i-1)与当前单词的组合dp[i-1][j],和当前字符串与前一个单词组合dp[i][j-1]这两种组合的最大值。即:dp[i][j] = max(dp[i-1][j],dp[i][j-1]).

3.返回值:sentence.length() - dp[m][n];

4.状态压缩:二维dp数组可以压缩为一维数组。

5.如果有帮到您,请不吝啬点赞~

class Solution {
    public int respace(String[] dictionary, String sentence) {
        int m = sentence.length();
        int[] dp = new int[m+1];
        for(int i=1;i<=m;i++){                 //外层循环字符串
            for(String word:dictionary){             //内层循环字典
                int len = word.length();
                if(i >= len && word.equals(sentence.substring(i-len,i))){
                    dp[i] = Math.max(dp[i],dp[i-len]+len);  //状态转移
                }else{
                    dp[i] = Math.max(dp[i],dp[i-1]);
                }
            }
        }
        return m-dp[m];
    }
}

Q17.14 最小K个数

设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。

示例:

输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]
提示:

0 <= len(arr) <= 100000
0 <= k <= min(100000, len(arr))

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/smallest-k-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

快速排序递归版

class Solution {
    public int[] smallestK(int[] arr, int k) {
        select(arr, 0, arr.length - 1, k);
        int[] ans = new int[k];
        System.arraycopy(arr, 0, ans, 0, k);
        return ans;
    }

    public void select(int[] arr, int p, int q, int k) {
        if (p >= q) return;
        int l = p, r = q;
        int m = arr[p];
        while (l < r) {
            while (l < r && arr[r] >= m) {
                r--;
            }
            arr[l] = arr[r];
            while (l < r && arr[l] <= m) {
                l++;
            }
            arr[r] = arr[l];
        }
        arr[l] = m;
        int t = l - p + 1;
        if (t == k) return;
        if (t < k) {
            select(arr, l + 1, q, k - t);
        } else {
            select(arr, p, l - 1, k);
        }
    }
}

Q17.15 最长单词

给定一组单词words,编写一个程序,找出其中的最长单词,且该单词由这组单词中的其他单词组合而成。若有多个长度相同的结果,返回其中字典序最小的一项,若没有符合要求的单词则返回空字符串。

示例:

输入: ["cat","banana","dog","nana","walk","walker","dogwalker"]
输出: "dogwalker"
解释: "dogwalker"可由"dog"和"walker"组成。
提示:

0 <= len(words) <= 200
1 <= len(words[i]) <= 100

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-word-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路简单:

按照长度降序排序,如果长度相等就按照字典序排序。这样的话如果每个单词都是组合单词,那么最前面的单词就是题目要求的返回单词。
遍历排序后的单词数组,如果是复合单词就返回
复合单词检测思路: 目标单词的前i个字母是单词列表里面的单词 && 剩下的字母是组合单词

class Solution {
    // 检测word是否是由set里面的单词组成
    private boolean isComposeWord(Set<String> set, String word) {
        if (word.length() == 0) {
            return true;
        }

        for (int i = 0; i < word.length(); i++) {
            if (set.contains(word.substring(0, i + 1)) && isComposeWord(set, word.substring(i + 1))) {
                return true;
            }
        }
        return false;
    }

    public String longestWord(String[] words) {
        // 将单词列表按照长度降序,长度相同按照字典序
        Arrays.sort(words, (o1, o2) -> {
            if (o1.length() == o2.length()) {
                return o1.compareTo(o2);
            } else {
                return Integer.compare(o2.length(), o1.length());
            }
        });

        // 把数组做成set,提高检测的效率
        Set<String> set = new HashSet<>(Arrays.asList(words));
        // 遍历检测当前单词是不是组合单词,是的话直接返回
        for (String  word :words ) {
            set.remove(word);
            if (isComposeWord(set, word)) {
                return word;
            }
            set.add(word);
        }
        return "";
    }
}

Q17.16 按摩师

一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。

注意:本题相对原题稍作改动

 

示例 1:

输入: [1,2,3,1]
输出: 4
解释: 选择 1 号预约和 3 号预约,总时长 = 1 + 3 = 4。
示例 2:

输入: [2,7,9,3,1]
输出: 12
解释: 选择 1 号预约、 3 号预约和 5 号预约,总时长 = 2 + 9 + 1 = 12。
示例 3:

输入: [2,1,4,5,3,1,1,3]
输出: 12
解释: 选择 1 号预约、 3 号预约、 5 号预约和 8 号预约,总时长 = 2 + 4 + 3 + 3 = 12。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/the-masseuse-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

递推方程:dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])

class Solution {
    public int massage(int[] nums) {
        int n = nums.length;
        if (n == 0) {
            return 0;
        }
        if (n == 1) {
            return nums[0];
        }
        int[] dp = new int[n];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0], nums[1]);
        for (int i = 2; i < n; i++) {
            dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
        }
        return dp[n - 1];
    }
}

Q17.17 多次搜索

给定一个较长字符串big和一个包含较短字符串的数组smalls,设计一个方法,根据smalls中的每一个较短字符串,对big进行搜索。输出smalls中的字符串在big里出现的所有位置positions,其中positions[i]为smalls[i]出现的所有位置。

示例:

输入:
big = "mississippi"
smalls = ["is","ppi","hi","sis","i","ssippi"]
输出: [[1,4],[8],[],[3],[1,4,7,10],[5]]
提示:

0 <= len(big) <= 1000
0 <= len(smalls[i]) <= 1000
smalls的总字符数不会超过 100000。
你可以认为smalls中没有重复字符串。
所有出现的字符均为英文小写字母。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/multi-search-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

public int[][] multiSearch(String big, String[] smalls) {
        if (smalls == null || big == null || smalls.length == 0) return new int[0][];
        List<List<Integer>> tempRes = new LinkedList<>();
        for (String small : smalls) {
            List<Integer> tmp = new LinkedList<>();
            if (small.length() == 0) {
                tempRes.add(tmp);
                continue;
            }
            int index = 0;
            while (index < big.length() && big.indexOf(small, index) != -1) {
                int pos = big.indexOf(small, index);
                tmp.add(pos);
                index = pos + 1;
            }
            tempRes.add(tmp);
        }
        int[][] res = new int[tempRes.size()][];
        for (int i = 0; i < res.length; i++) {
            int[] ints = new int[tempRes.get(i).size()];
            for (int i1 = 0; i1 < ints.length; i1++) {
                ints[i1] = tempRes.get(i).get(i1);
            }
            res[i] = ints;
        }
        return res;
    }

Q17.18 最短超串

假设你有两个数组,一个长一个短,短的元素均不相同。找到长数组中包含短数组所有的元素的最短子数组,其出现顺序无关紧要。

返回最短子数组的左端点和右端点,如有多个满足条件的子数组,返回左端点最小的一个。若不存在,返回空数组。

示例 1:

输入:
big = [7,5,9,0,2,1,3,5,7,9,1,1,5,8,8,9,7]
small = [1,5,9]
输出: [7,10]
示例 2:

输入:
big = [1,2,3]
small = [4]
输出: []
提示:

big.length <= 100000
1 <= small.length <= 100000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shortest-supersequence-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

滑动窗口

class Solution {
    public int[] shortestSeq(int[] big, int[] small) {
        Map<Integer,Integer> window = new HashMap<>(); //window记录窗口内元素的情况
        Set<Integer> set = new HashSet<>(); //set作为查找表
        
        for(int e : small)
            set.add(e);
        
        int[] res = {big.length,big.length};
        int minLen = big.length + 1;
        
        for(int l=0,r=0;r<big.length;r++) {
            if(set.contains(big[r])) //考虑big[r]要不要进窗口【在set中的值才允许进入】
                window.put(big[r],window.getOrDefault(big[r],0)+1); 
            
            while(window.size()==set.size()) { //当窗口符合要求,左边界收缩,注意要不要更新window
                if(set.contains(big[l])) { //此时,需要更新window
                    int n = window.get(big[l]);
                    if(n==1) {
                        window.remove(big[l]); //。。。
                        if(r-l+1<minLen) { //看要不要更新答案
                            res[0] = l;
                            res[1] = r;
                            minLen = r-l+1;
                        }
                    }else {
                        window.put(big[l],n-1);
                    }
                }
                l++;
            }
        }
        
        return res[0]==big.length?new int[0]:res;
    }
}

Q17.19 消失的两个数字

给定一个数组,包含从 1 到 N 所有的整数,但其中缺了两个数字。你能在 O(N) 时间内只用 O(1) 的空间找到它们吗?

以任意顺序返回这两个数字均可。

示例 1:

输入: [1]
输出: [2,3]
示例 2:

输入: [2,3]
输出: [1,4]
提示:

nums.length <= 30000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/missing-two-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

class Solution {
    boolean length1 = false;
    public int[] missingTwo(int[] nums) {
        int[] result = new int[2];
        for(int i = 0 ; i < nums.length ; i++){
            if(nums[i] != i + 1){
                if(nums[i] < nums.length + 1){
                    int temp = nums[i];
                    nums[i] = nums[temp - 1];
                    nums[temp - 1] = temp;
                    i--;
                }else{
                    if(nums[i] == nums.length + 1){
                        length1 = true;
                    }
                }
            }
        }

        for(int i = 0 ; i < nums.length ; i++){
            if(nums[i] != i + 1){
                if(result[0] == 0){
                    result[0] = i + 1;
                }else{
                    result[1] = i + 1;
                    return result;
                }
            }
        }
        if(result[0] == 0){
            result[0] = nums.length + 1;
            result[1] = nums.length + 2;
        }else{
            if(length1){
                result[1] = nums.length + 2;
            }else{
                result[1] = nums.length + 1;
            }
        }
        return result;
    }
}

Q17.20 消失的两个数字

随机产生数字并传递给一个方法。你能否完成这个方法,在每次产生新值时,寻找当前所有值的中间值(中位数)并保存。

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

例如,

[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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/continuous-median-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

利用一个大顶堆一个小顶堆,中位数可以看作是隔开两个数组的分位线,这样左端用大顶堆,右端用小顶堆,最后两个堆(奇数个是一个)的peek求平均即可。

class MedianFinder {

    /** initialize your data structure here. */
    PriorityQueue<Integer> left, right;
    boolean isLeft;

    public MedianFinder() {

        left = new PriorityQueue<>((x, y) -> y - x);
        right = new PriorityQueue<>();
    }
    
    public void addNum(int num) {
        
        left.offer(num);
        right.offer(left.poll());

        if (left.size() < right.size())
            left.offer(right.poll());
    }
    
    public double findMedian() {

        if (left.size() > right.size())
            return left.peek();
        
        return (left.peek() + right.peek()) / 2.0;
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值