作者: CheungQ

LeetCode刷题【1870】准时到达的列车最小时速

给你一个浮点数 hour ,表示你到达办公室可用的总通勤时间。要到达办公室,你必须按给定次序乘坐 n 趟列车。另给你一个长度为 n 的整数数组 dist ,其中 dist[i] 表示第 i 趟列车的行驶距离(单位是千米)。

每趟列车均只能在整点发车,所以你可能需要在两趟列车之间等待一段时间。

  • 例如,第 1 趟列车需要 1.5 小时,那你必须再等待 0.5 小时,搭乘在第 2 小时发车的第 2 趟列车。

返回能满足你准时到达办公室所要求全部列车的 最小正整数 时速(单位:千米每小时),如果无法准时到达,则返回 -1

生成的测试用例保证答案不超过 107 ,且 hour小数点后最多存在两位数字

 

示例 1:

输入:dist = [1,3,2], hour = 6
输出:1
解释:速度为 1 时:
- 第 1 趟列车运行需要 1/1 = 1 小时。
- 由于是在整数时间到达,可以立即换乘在第 1 小时发车的列车。第 2 趟列车运行需要 3/1 = 3 小时。
- 由于是在整数时间到达,可以立即换乘在第 4 小时发车的列车。第 3 趟列车运行需要 2/1 = 2 小时。
- 你将会恰好在第 6 小时到达。

示例 2:

输入:dist = [1,3,2], hour = 2.7
输出:3
解释:速度为 3 时:
- 第 1 趟列车运行需要 1/3 = 0.33333 小时。
- 由于不是在整数时间到达,故需要等待至第 1 小时才能搭乘列车。第 2 趟列车运行需要 3/3 = 1 小时。
- 由于是在整数时间到达,可以立即换乘在第 2 小时发车的列车。第 3 趟列车运行需要 2/3 = 0.66667 小时。
- 你将会在第 2.66667 小时到达。

示例 3:

输入:dist = [1,3,2], hour = 1.9
输出:-1
解释:不可能准时到达,因为第 3 趟列车最早是在第 2 小时发车。

 

提示:

  • n == dist.length
  • 1 <= n <= 105
  • 1 <= dist[i] <= 105
  • 1 <= hour <= 109
  • hours 中,小数点后最多存在两位数字
Related Topics
  • 数组
  • 二分查找
  • \n
  • 👍 16
  • 👎 0
  • 题解

    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
    
    
        public int minSpeedOnTime(int[] dist, double hour) {
    
            int l=1;
            int r = 10000000;
            int lastAvailableSpeed = -1;
            while (l<r){
                int mid = l + (r-l)/2;
                if (canArrive(dist,mid,hour)){
                    lastAvailableSpeed = mid;
                    r = mid;
                }else{
                    l = mid+1;
                }
            }
            return canArrive(dist,l,hour)?l:lastAvailableSpeed;
        }
    
    
        private boolean canArrive(int[] distArr, int speed, double hourLimit){
            double totalHour = 0;
            for (int i = 0; i < distArr.length; i++) {
                if (i==distArr.length-1){
                    totalHour += (double)distArr[i]/speed;
                }else{
                    totalHour += Math.ceil((double)distArr[i]/speed);
                }
            }
            return totalHour*1.0 <= hourLimit;
        }
    
    }
    //leetcode submit region end(Prohibit modification and deletion)
    

    写好判断规则,剩下的就是二分查找的工作,比较简单。

    不过最后一个用例

    测试用例:[1,1,100000]
    2.01

    被坑了会儿,最终left应该还可以再取一次判断,有可能最后left=mid+1的值等于right,但是被while的条件过滤掉了。

    此外这题里还有一个坑,就是浮点运算的时候精度丢失的问题,不小心就容易忽略。

    对二分法查找,目前虽然会用了,但是对于left,right的+1还是-1的时机还是有点模糊,以及最终是要取left的值还是取right的值也有点拎不清。还需多加练习理解努力。

    LeetCode刷题【剑指 Offer 38】字符串的排列

    输入一个字符串,打印出该字符串中字符的所有排列。

     

    你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

     

    示例:

    输入:s = "abc"
    输出:["abc","acb","bac","bca","cab","cba"]
    

     

    限制:

    1 <= s 的长度 <= 8

    Related Topics
  • 字符串
  • 回溯
  • \n
  • 👍 391
  • 👎 0
  • 题解

    看起来有点面熟,很直白的深度搜索回溯算法。

    根据所给字符串s,挨个位置遍历,在新得出的字符串的每一位上穷举,并记录已举过的字符串,在穷举的时候,如果已经存在于已使用过的位置的集合的记录中,则跳过。

    而最终要求“里面不能有重复元素”,所以最终的结果集用set集合保存,直接进行去重。

    class Solution {
    
    
        private HashSet<String> list;
    
        private Integer length;
    
        public String[] permutation(String s) {
            list = new HashSet<>();
            length = s.length();
            HashSet<Integer> visited = new HashSet<>();
            dfs(s.toCharArray(),visited,0,new char[s.length()]);
            return list.toArray(new String[0]);
        }
    
    
        private void dfs(char[] charArr,HashSet<Integer> visited,int index,char[] newCharArr){
            if (index==length){
                list.add(new String(newCharArr));
                return;
            }
            for (int i = 0; i < charArr.length; i++) {
                if (visited.contains(i)){
                    continue;
                }
                visited.add(i);
                newCharArr[index] = charArr[i];
                dfs(charArr,visited,index+1,newCharArr);
                visited.remove(i);
            }
        }
    }

    结果

    解答成功:
    执行耗时:69 ms,击败了10.93% 的Java用户
    内存消耗:44.2 MB,击败了21.26% 的Java用户

    而在结果集没有使用set集合的时候,跑这样一个用例出了问题。

    测试用例:"aab"
    测试结果:["aab","aba","aab","aba","baa","baa"]
    期望结果:["aba","aab","baa"]

    所以,取第一个a还是第二个a是没有区别的,这边之前记录的是HashSet<Integer> visited字符串的索引位置,所以在此之外,可以再借助一个字符串集合进行剪枝操作Set<Character> sameChar。剪枝之后的结果集也可以不用set集合了,直接更简单的List集合替代。

    另外还有一点小小的改动,一开始写的visited是Set集合,而记录内容是某个索引位置是否访问过,都知道索引的特征是从0-N连续的,这边可以再简化成一个boolean[] visited数组。

    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
    
    
        private List<String> list;
    
        private Integer length;
    
        public String[] permutation(String s) {
            list = new ArrayList<>();
            length = s.length();
            boolean[] visited = new boolean[s.length()];
            dfs(s.toCharArray(),visited,0,new char[s.length()]);
            String[] res = new String[list.size()];
            int i = 0;
            for (String s1 : list) {
                res[i] = s1;
                i++;
            }
            return res;
        }
    
    
        private void dfs(char[] charArr,boolean[] visited,int index,char[] newCharArr){
            if (index==length){
                list.add(new String(newCharArr));
                return;
            }
            Set<Character> sameChar = new HashSet<>();
            for (int i = 0; i < charArr.length; i++) {
                if (visited[i]){
                    continue;
                }
                if (sameChar.contains(charArr[i])){
                    continue;
                }
                sameChar.add(charArr[i]);
                visited[i] = true;
                newCharArr[index] = charArr[i];
                dfs(charArr,visited,index+1,newCharArr);
                visited[i] = false;
            }
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)
    
    
    解答成功:
    执行耗时:14 ms,击败了64.61% 的Java用户
    内存消耗:42.7 MB,击败了73.19% 的Java用户

    LeetCode刷题【169】多数元素

    给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

    你可以假设数组是非空的,并且给定的数组总是存在多数元素。

     

    示例 1:

    输入:[3,2,3]
    输出:3

    示例 2:

    输入:[2,2,1,1,1,2,2]
    输出:2
    

     

    进阶:

    • 尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
    Related Topics
  • 数组
  • 哈希表
  • 分治
  • 计数
  • 排序
  • \n
  • 👍 1075
  • 👎 0
  • 题解

    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public int majorityElement(int[] nums) {
            int maj = nums[0];
            int count = 1;
            for (int i = 1; i < nums.length; i++) {
                if (nums[i]==maj){
                    count++;
                }else{
                    count--;
                    if (count==0){
                        maj=nums[i];
                        count=1;
                    }
                }
            }
            return maj;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)
    

    看到这个Boyer-Moore摩尔投票的解法的时候,确实惊艳到了,虽然只是个简单题。

    根据题中描述

    你可以假设数组是非空的,并且给定的数组总是存在多数元素。

    题中的众数必定是超过半数的,及时数量总和为偶数的情况下,众数的数量也一定是超过一半的,不会等于1/2。

    一开始看到这个解法有点懵,画个图辅助理解下。每一种颜色代表一个数字,每一个格子代表给定的数组中有一个这个数字。

    从图中可以很明显看到,蓝为众数,那么按照摩尔投票方法的作法,随机取一个格子,然后在其他颜色区域的内容中,随机找一个格子和他相对抵消。在这里我们用数字x和对应的抵消数字x’来标示。

    那么其实很明显了,最终还有空余,没有被全部抵消掉的颜色只有蓝色了,这个蓝色代表的数字必然是众数。

    另外还是说下常规解法,就写一个比较好想的hashmap统计了。

    
    import java.util.HashMap;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public int majorityElement(int[] nums) {
            int half = nums.length>>1;
            HashMap<Integer,Integer> map = new HashMap<>();
            for (int num : nums) {
                int c = 1;
                if (map.containsKey(num)){
                    c = map.get(num)+1;
                }
                if (c > half){
                    return num;
                }
                map.put(num,c);
            }
    
            return 0;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    LeetCode刷题【543】二叉树的直径

    给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

     

    示例 :
    给定二叉树

              1
             / \
            2   3
           / \     
          4   5    
    

    返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

     

    注意:两结点之间的路径长度是以它们之间边的数目表示。

    Related Topics
  • 深度优先搜索
  • 二叉树
  • \n
  • 👍 747
  • 👎 0
  • 题解

    求最长直径,且这个直径可能不经过root节点,看起来应该是深度搜索。先把深搜模板写出来看下下一步怎么办,定义一个length在不断深搜遍历过程中取到最大的直径的数值。

    int length = 0;
    
        public int diameterOfBinaryTree(TreeNode root) {
            if (root==null){
                return 0;
            }
            dfs(root);
            return length;
        }
    
        private void dfs(TreeNode node){
            if (node.left!=null){
                dfs(node.left);
            }
            if (node.right!=null){
                dfs(node.right);
            }
            return;
        }

    根据题意,当前节点的最大直径的路径为左边最大深度+当前节点+右边最大深度,所以在dfs算法中递归左子树和递归右子树的时候应当取到他们的对应子树的最大深度。那么这个时候改下dfs方法。

    private int dfs(TreeNode node){
            int leftHeight = 0;
            int rightHeight = 0;
            if (node.left!=null){
                leftHeight = dfs(node.left);
            }
            if (node.right!=null){
                rightHeight = dfs(node.right);
            }
            return Math.max(rightHeight,leftHeight)+1;
        }

    最后当然要return Math.max(rightHeight,leftHeight)+1;取到当前节点下的左子树和右子树中深度更深的那个子树的深度,再加上当前节点的深度+1;

    而在这个时候就可以发现,左子树右子树的深度有了,就可以得到经过当前节点的最大直径的长度了,顺便重命名下length为maxLength,只要拿已存的maxLength和当前的maxLength比较得到更大的值重新赋值给maxLength就可以了。

    private int dfs(TreeNode node){
            int leftHeight = 0;
            int rightHeight = 0;
            if (node.left!=null){
                leftHeight = dfs(node.left);
            }
            if (node.right!=null){
                rightHeight = dfs(node.right);
            }
            maxLength = Math.max(maxLength,leftHeight+rightHeight+1);
            return Math.max(rightHeight,leftHeight)+1;
        }

    跑一下用例

    			测试用例:[1,2,3,4,5]
    			测试结果:4
    			期望结果:3

    不大对劲,差在哪里呢?哦

    返回 3, 它的长度是路径 [4,2,1,3] 

    路径长度应该是节点数减1

    最终代码如下

    
    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode() {}
     *     TreeNode(int val) { this.val = val; }
     *     TreeNode(int val, TreeNode left, TreeNode right) {
     *         this.val = val;
     *         this.left = left;
     *         this.right = right;
     *     }
     * }
     */
    class Solution {
    
        int maxLength = 0;
    
        public int diameterOfBinaryTree(TreeNode root) {
            if (root==null){
                return 0;
            }
            dfs(root);
            return maxLength-1;
        }
    
        private int dfs(TreeNode node){
            int leftHeight = 0;
            int rightHeight = 0;
            if (node.left!=null){
                leftHeight = dfs(node.left);
            }
            if (node.right!=null){
                rightHeight = dfs(node.right);
            }
            maxLength = Math.max(maxLength,leftHeight+rightHeight+1);
            return Math.max(rightHeight,leftHeight)+1;
        }
    
    
    
    
    }
    //leetcode submit region end(Prohibit modification and deletion)
    

    LeetCode刷题【781】森林中的兔子

    森林中,每个兔子都有颜色。其中一些兔子(可能是全部)告诉你还有多少其他的兔子和自己有相同的颜色。我们将这些回答放在 answers 数组里。

    返回森林中兔子的最少数量。

    示例:
    输入: answers = [1, 1, 2]
    输出: 5
    解释:
    两只回答了 "1" 的兔子可能有相同的颜色,设为红色。
    之后回答了 "2" 的兔子不会是红色,否则他们的回答会相互矛盾。
    设回答了 "2" 的兔子为蓝色。
    此外,森林中还应有另外 2 只蓝色兔子的回答没有包含在数组中。
    因此森林中兔子的最少数量是 5: 3 只回答的和 2 只没有回答的。
    
    输入: answers = [10, 10, 10]
    输出: 11
    
    输入: answers = []
    输出: 0
    

    说明:

    1. answers 的长度最大为1000
    2. answers[i] 是在 [0, 999] 范围内的整数。
    Related Topics
  • 贪心
  • 哈希表
  • 数学
  • \n
  • 👍 189
  • 👎 0
  • 题解

    题中给出了 answers[i]的范围在0-999,所以我在声明numCount数组的时候多加了一位,用来保存最终结果,也可以单独声明一个变量。顺便存在一个数组里的作法只是图省个事而已。

    那么来分析一下,要求最小可能的数量,可以假定,当前n+1次遇到说和自己颜色一样的兔子还有n个的时候,都是同一个颜色的兔子。

    如果在遇到了n+1个兔子之后还遇到了说和自己颜色一样的兔子还有n个的时候,我们可以认为这只兔子的颜色和前面说n只的颜色是不一样的。那么就应该有另外n+1只后面这个颜色的兔子。

    定义一个numCount数组,key就是说还有n,当第一次遇到的时候,就给加上n+1只的结果,同时value值统计遇到了几只说n的兔子,如果统计有n+1只说了n的兔子之后,就置零,后面再遇到就是其他的颜色了

    public class LeetCode781 {
        public int numRabbits(int[] answers) {
            int[] numCount = new int[1001];
            for (int num : answers) {
                if(numCount[num] == 0){
                    //没有遇到过,加上下数量
                    //如果大于0,则说明已经遇到过不再重复计数
                    numCount[1000] += num+1;
                }
                //对应数量的+1
                numCount[num]++;
                if (numCount[num] == num+1){
                    //已经有N+1只兔子说了有N只和自己颜色一样
                    //这时候可以认为,这种颜色的最小可能数量已经达到了
                    //可以清空,后面再有说这个数量的可以认为是其他的颜色
                    numCount[num] = 0;
                }
            }
            return numCount[1000];
        }
    }
    

    LeetCode刷题【328】奇偶链表

    给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

    请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

    示例 1:

    输入: 1->2->3->4->5->NULL
    输出: 1->3->5->2->4->NULL
    

    示例 2:

    输入: 2->1->3->5->6->4->7->NULL 
    输出: 2->3->6->7->1->5->4->NULL

    说明:

    • 应当保持奇数节点和偶数节点的相对顺序。
    • 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
    Related Topics
  • 链表
  • \n
  • 👍 447
  • 👎 0
  • 题解

    
    /**
     * Definition for singly-linked list.
     * public class ListNode {
     *     int val;
     *     ListNode next;
     *     ListNode() {}
     *     ListNode(int val) { this.val = val; }
     *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
     * }
     */
    class Solution {
        public ListNode oddEvenList(ListNode head) {
            if (head == null || head.next == null) return head;
            ListNode n = head;
            ListNode n2 = head.next;
            ListNode n2head = n2;
            while (true) {
                if (n2.next != null) {
                    n.next = n2.next;
                    n = n.next;
                    if (n.next != null) {
                        n2.next = n.next;
                        n2 = n2.next;
                    } else {
                        break;
                    }
                } else {
                    break;
                }
            }
            n2.next=null;
            n.next = n2head;
            return head;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)
    
            解答成功:
            执行耗时:0 ms,击败了100.00% 的Java用户
            内存消耗:38.2 MB,击败了18.05% 的Java用户

    借助n,n2构建奇偶链表,每次从head向后移动2位,第一个是奇数位,第二个是偶数位,判断好有没有下一个的情况。在不断迭代循环中,依次建立新的关联关系。

    同时因为n,n2是不断向后迭代遍历的,需要借助一个n2head来保存偶数位的链表头,最终有个n2.next=null;切断偶数位再链接回原来他后一位的奇数位的关联关系。再此时n为奇数位的末位,next指向之前创建的偶数位链表的首位。

    重新捋下代码,把while中的两个if条件提到while条件中,那么两个break分支就可以去掉了。这边可能会有疑虑,如果n.next!=null而n2.next=null的情况,会被错误的跳过,但是实际情况是这个分支场景会被下面的n.next = n2head覆盖替换掉,所以无需担心。同样的n2.next=null要处理的切断环链的操作会在while循环里被n2.next = n.next;处理掉。

    最终优化过的代码

    
    //leetcode submit region begin(Prohibit modification and deletion)
    
    
    /**
     * Definition for singly-linked list.
     * public class ListNode {
     *     int val;
     *     ListNode next;
     *     ListNode() {}
     *     ListNode(int val) { this.val = val; }
     *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
     * }
     */
    class Solution {
        public ListNode oddEvenList(ListNode head) {
            if (head == null || head.next == null) return head;
            ListNode n = head;
            ListNode n2 = head.next;
            ListNode n2head = n2;
            while (n.next != null && n2.next != null) {
                    n.next = n2.next;
                    n = n.next;
                    n2.next = n.next;
                    n2 = n2.next;
            }
            n.next = n2head;
            return head;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    提交结果

            解答成功:
            执行耗时:0 ms,击败了100.00% 的Java用户
            内存消耗:37.8 MB,击败了93.06% 的Java用户

    LeetCode刷题【66】加一

    给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。

    最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。

    你可以假设除了整数 0 之外,这个整数不会以零开头。

     

    示例 1:

    输入:digits = [1,2,3]
    输出:[1,2,4]
    解释:输入数组表示数字 123。
    

    示例 2:

    输入:digits = [4,3,2,1]
    输出:[4,3,2,2]
    解释:输入数组表示数字 4321。
    

    示例 3:

    输入:digits = [0]
    输出:[1]
    

     

    提示:

    • 1 <= digits.length <= 100
    • 0 <= digits[i] <= 9
    Related Topics
  • 数组
  • 数学
  • \n
  • 👍 723
  • 👎 0
  • 题解

    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public int[] plusOne(int[] digits) {
            digits[digits.length-1] = digits[digits.length-1]+1;
            boolean will = false;
            for (int i = digits.length - 1; i >= 0; i--) {
                if (digits[i]>9){
                    will = true;
                    digits[i] = digits[i]-10;
                    if (i > 0){
                        digits[i-1] = digits[i-1]+1;
                    }
                }else{
                    will = false;
                }
            }
            if (will){
                int[] arr = new int[digits.length+1];
                System.arraycopy(digits, 0, arr, 1, digits.length);
                arr[0] = 1;
                return arr;
            }else{
                return digits;
            }
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    额,再看看大佬的题解

    class Solution {
        public int[] plusOne(int[] digits) {
            for (int i = digits.length - 1; i >= 0; i--) {
                digits[i]++;
                digits[i] = digits[i] % 10;
                if (digits[i] != 0) return digits;
            }
            digits = new int[digits.length + 1];
            digits[0] = 1;
            return digits;
        }
    }
    

    头皮发麻。。从后往前遍历,因为只加1,有变更如果不进位,则可以直接返回当前数组作为结果,如果要进位,则前一位继续加一。

    到了最后,如果每一位都加1了,说明是9,99,999这种情况,则构建一个新的比原来长以为的数组,首位赋值为1,后面的所有位数默认是0。

    符合结果。