月度归档: 2021年9月

LeetCode刷题【382】链表随机节点

给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样

进阶:
如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现?

示例:

// 初始化一个单链表 [1,2,3].
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
Solution solution = new Solution(head);

// getRandom()方法应随机返回1,2,3中的一个,保证每个元素被返回的概率相等。
solution.getRandom();
Related Topics
  • 水塘抽样
  • 链表
  • 数学
  • 随机化

  • 👍 151
  • 👎 0
  • 题解

    简单啊,,,随机么。直接存成数组,然后一个

    Math.random()

    出来了

    class Solution2 {
    
        List<ListNode> list;
        /** @param head The linked list's head.
        Note that the head is guaranteed to be not null, so it contains at least one node. */
        public Solution2(ListNode head) {
            list = new ArrayList<>();
            while (head!=null){
                list.add(head);
                head = head.next;
            }
        }
    
        /** Returns a random node's value. */
        public int getRandom() {
            int random = (int)(Math.random() * list.size());
            return list.get(random).val;
        }
    }

    进阶。不用总长度怎么取随机,最近好像遇到好几次了,特地花了点时间去仔细理解了下水塘抽样随机算法。本题题解如下

    class Solution {
    
        ListNode head;
        Random random;
        /** @param head The linked list's head.
            Note that the head is guaranteed to be not null, so it contains at least one node. */
        public Solution(ListNode head) {
            this.head = head;
            random = new Random();
        }
        
        /** Returns a random node's value. */
        public int getRandom() {
            ListNode node = head;
            int count = 1;
            int val = node.val;
            while (node!=null){
                if (random.nextInt(count) == 1){
                    val =node.val;
                }
                count++;
                node = node.next;
            }
            return val;
        }
    }

    LeetCode刷题【77】组合

    给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

    你可以按 任何顺序 返回答案。

     

    示例 1:

    输入:n = 4, k = 2
    输出:
    [
      [2,4],
      [3,4],
      [2,3],
      [1,2],
      [1,3],
      [1,4],
    ]

    示例 2:

    输入:n = 1, k = 1
    输出:[[1]]

     

    提示:

    • 1 <= n <= 20
    • 1 <= k <= n
    Related Topics
  • 数组
  • 回溯

  • 👍 696
  • 👎 0
  • 题解

    常规DFS回溯作法,类似的可以参考下LeetCode刷题【47】全排列 II,基本非常雷同

    class Solution {
        List<List<Integer>> arr;
        int k;
        int n;
        Set<Integer> integerSet;
        public List<List<Integer>> combine(int n, int k) {
            arr = new ArrayList<>();
            this.k = k;
            this.n = n;
            integerSet = new HashSet<>();
            dfs(new ArrayList<>());
            return arr;
        }
    
        private void dfs(List<Integer> list){
            if(list.size() == k){
                arr.add(new ArrayList<>(list));
                return;
            }
            int i;
            if (list.size()==0){
                i=1;
            }else{
                i= list.get(list.size()-1) + 1;
            }
            for (; i <= n; i++) {
                if (integerSet.contains(i) ){
                    continue;
                }
                integerSet.add(i);
                list.add(i);
                dfs(list);
                list.remove(list.size()-1);
                integerSet.remove(i);
            }
        }
    
    }

    LeetCode刷题【502】IPO

    假设 力扣(LeetCode)即将开始 IPO 。为了以更高的价格将股票卖给风险投资公司,力扣 希望在 IPO 之前开展一些项目以增加其资本。 由于资源有限,它只能在 IPO 之前完成最多 k 个不同的项目。帮助 力扣 设计完成最多 k 个不同项目后得到最大总资本的方式。

    给你 n 个项目。对于每个项目 i ,它都有一个纯利润 profits[i] ,和启动该项目需要的最小资本 capital[i]

    最初,你的资本为 w 。当你完成一个项目时,你将获得纯利润,且利润将被添加到你的总资本中。

    总而言之,从给定项目中选择 最多 k 个不同项目的列表,以 最大化最终资本 ,并输出最终可获得的最多资本。

    答案保证在 32 位有符号整数范围内。

     

    示例 1:

    输入:k = 2, w = 0, profits = [1,2,3], capital = [0,1,1]
    输出:4
    解释:
    由于你的初始资本为 0,你仅可以从 0 号项目开始。
    在完成后,你将获得 1 的利润,你的总资本将变为 1。
    此时你可以选择开始 1 号或 2 号项目。
    由于你最多可以选择两个项目,所以你需要完成 2 号项目以获得最大的资本。
    因此,输出最后最大化的资本,为 0 + 1 + 3 = 4。
    

    示例 2:

    输入:k = 3, w = 0, profits = [1,2,3], capital = [0,1,2]
    输出:6
    

     

    提示:

    • 1 <= k <= 105
    • 0 <= w <= 109
    • n == profits.length
    • n == capital.length
    • 1 <= n <= 105
    • 0 <= profits[i] <= 104
    • 0 <= capital[i] <= 109
    Related Topics
  • 贪心
  • 数组
  • 排序
  • 堆(优先队列)

  • 👍 118
  • 👎 0
  • 题解

    从题面来理解,第一步,从所有项目中找到本金小于等于当前拥有的总的本金的项目,这些项目为可投资项目。第二步,由于给出的是纯利润,且题面并没有要求说本金最大化优化使用,那么就可以最简单的直接选利润最大的一个项目来投,等利润拿到了,再去投资下一个项目。

    而如果要求本金最大化优化使用的话,可能要考虑一次投资多个项目,什么样的项目组合能得到最大化利润,这样的要求就更加复杂话了,而实际的企业投资操作比这个更加复杂,更多的风险控制,不是什么能套个公式算算能到结论的。

    好的,既然上面思路理出来了,我们知道提供出来的profits和capital是索引对应关系,所以第一步的操作中,可以维护一个堆,堆的内容和capital的索引和本金的值,这样取的时候从顶部取出所有小于当前拥有的本金的值。

    接下来第二步,前面我们已经拿到了所有的符合投资要求的索引了,所以这里,我们对应的也建一个堆,按照profits利润值降序排列。然后从顶部取出一个,算出新的总的本金金额。

    到这里编码思路就已经出来了。

    class Solution {
        public int findMaximizedCapital(int k, int w, int[] profits, int[] capital) {
            int amount = w;
            //堆1成本降序
            //内部结构[0:索引,1:成本]
            //堆1中目前成本越小的越排在前面,便于下一步寻找符合成本要求的项目
            PriorityQueue<int[]> queue1 = new PriorityQueue<>(new Comparator<int[]>() {
                @Override
                public int compare(int[] o1, int[] o2) {
                    return o1[1]-o2[1];
                }
            });
            for (int i = 0; i < capital.length; i++) {
                queue1.offer(new int[]{i,capital[i]});
            }
    
            //queue2
            //int[0] 成本  int[1]利润  利润越大的越在前面
            PriorityQueue<int[]> queue2 = new PriorityQueue<>(new Comparator<int[]>() {
                @Override
                public int compare(int[] o1, int[] o2) {
                    return o2[1]-o1[1];
                }
            });
    
            while (k>0){
                while (!queue1.isEmpty() && queue1.peek()[1] <= amount){
                    //arr[0]索引位,arr[1]是成本位
                    int[] arr = queue1.poll();
                    queue2.offer(new int[]{arr[1],profits[arr[0]]});
                }
                if (queue2.isEmpty()){
                    //没有能投的项目了,钱.......不够
                    break;
                }
                int[] arr2 = queue2.poll();
                k--;
                //赚了 arr2[1]
                amount +=arr2[1];
            }
            return amount;
        }
    }

    那么,你学废了嘛!~

    LeetCode刷题【86】分隔链表

    给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

    你应当 保留 两个分区中每个节点的初始相对位置。

     

    示例 1:

    输入:head = [1,4,3,2,5,2], x = 3
    输出:[1,2,2,4,3,5]
    

    示例 2:

    输入:head = [2,1], x = 2
    输出:[1,2]
    

     

    提示:

    • 链表中节点的数目在范围 [0, 200]
    • -100 <= Node.val <= 100
    • -200 <= x <= 200
    Related Topics
  • 链表
  • 双指针

  • 👍 454
  • 👎 0
  • 题解,嗯,简单直白双指针的解法,另外加一个链表常用小技巧虚拟头结点。

    因为头结点可能会被替换掉,所以再加一个虚拟头结点来链接到真正的头结点,这样可以保证有效快速的找到真正的新的头结点。

    第一个指针,从虚拟头结点开始,一直往后跑,找到第一个大于等于x值的之前的节点。

    第二个指针,从头结点开始往后遍历,找到比x小的值,把这个比x小的值的前一个节点指向当前节点的后一个节点,这样后面这块的粘合工作就算完成了。另一部分,把之前找到的第一个指针的next节点指向当前节点,并将当前节点的next指向原来第一个指针的后一个节点

    举例说明【F,1,4,3,2,5,2】,x=3

    最开始的时候第一个指针找到了1节点,因为后面的4比3大,此时可以保证这个1和之前的节点都比x小,所以从x开始往后找小于3的节点。

    一直找到了2节点,把3节点的next指向2后面的5

    把之前的1节点的next指向到2,把2的next指向到1节点原来的next节点4,并把第一个指针往后移动一位移动到2的位置,因为2也比3小

    此时新的顺序【F,1,2,4,3,5,2】

    继续这个过程往后找到另一个2节点

    把2的前一个节点的next指向到2的后面的节点NULL,

    把之前第一个指针的2的next指向到这里的2,并把这里的2的next指向到原来的2的next节点4,并把第一个指针往后移动一位到这个新的2节点

    此时新的顺序【F,1,2,2,4,3,5】,最后指针移动到NULL,结束。

    返回原来的虚拟节点的next节点。

    class Solution {
        public ListNode partition(ListNode head, int x) {
            if (head == null || head.next ==null)return head;
            ListNode fakeHead = new ListNode();
            fakeHead.next = head;
            ListNode smallerTail = fakeHead;
            while (smallerTail!=null && smallerTail.next != null && smallerTail.next.val < x){
                smallerTail = smallerTail.next;
            }
            if (smallerTail.next == null){
                return head;
            }
            ListNode current = smallerTail;
            while (current != null){
                if (current.next!=null && current.next.val < x){
                    ListNode tmp1 = smallerTail.next;
                    ListNode tmp2 = current.next;
                    current.next = current.next.next;
                    smallerTail.next = tmp2;
                    tmp2.next = tmp1;
                    smallerTail = smallerTail.next;
                }else{
                    current = current.next;
                }
            }
            return fakeHead.next;
        }
    }

    收工!~

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

    LeetCode刷题【384】打乱数组

    给你一个整数数组 nums ,设计算法来打乱一个没有重复元素的数组。

    实现 Solution class:

    • Solution(int[] nums) 使用整数数组 nums 初始化对象
    • int[] reset() 重设数组到它的初始状态并返回
    • int[] shuffle() 返回数组随机打乱后的结果

     

    示例:

    输入
    ["Solution", "shuffle", "reset", "shuffle"]
    [[[1, 2, 3]], [], [], []]
    输出
    [null, [3, 1, 2], [1, 2, 3], [1, 3, 2]]
    
    解释
    Solution solution = new Solution([1, 2, 3]);
    solution.shuffle();    // 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。例如,返回 [3, 1, 2]
    solution.reset();      // 重设数组到它的初始状态 [1, 2, 3] 。返回 [1, 2, 3]
    solution.shuffle();    // 随机返回数组 [1, 2, 3] 打乱后的结果。例如,返回 [1, 3, 2]
    

     

    提示:

    • 1 <= nums.length <= 200
    • -106 <= nums[i] <= 106
    • nums 中的所有元素都是 唯一的
    • 最多可以调用 5 * 104resetshuffle
    Related Topics
  • 数组
  • 数学
  • 随机化

  • 👍 154
  • 👎 0
  • 题解

    class Solution {
    
        int[] origin;
        int[] randArr;
    
        public Solution(int[] nums) {
            origin = nums;
            randArr = nums.clone();
        }
        
        /** Resets the array to its original configuration and return it. */
        public int[] reset() {
            randArr = origin.clone();
            return origin;
        }
        
        /** Returns a random shuffling of the array. */
        public int[] shuffle() {
            Random random = new Random();
            for (int i = 0; i < randArr.length; i++) {
                int randIndex = random.nextInt(i+1);
                int tmp = randArr[i];
                randArr[i] = randArr[randIndex];
                randArr[randIndex] = tmp;
            }
            return randArr;
        }
    }

    概率的东西有点陌生,高中大学的东西已经彻底全忘了,官方题解说的Fisher-Yates 洗牌算法也没太看懂,为啥这么洗完概率就一定是均等的。哎。。。挫败感。

    LeetCode刷题【234】回文链表

    给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false

     

    示例 1:

    输入:head = [1,2,2,1]
    输出:true
    

    示例 2:

    输入:head = [1,2]
    输出:false
    

     

    提示:

    • 链表中节点数目在范围[1, 105]
    • 0 <= Node.val <= 9

     

    进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

    Related Topics
  • 递归
  • 链表
  • 双指针

  • 👍 1106
  • 👎 0
  • 题解

    常规方法,声明一个新的数组,保存每个节点,方便找到中间节点之后从中间节点往前和往后两个放下查找对比,至于之前的有一个类似的查找链表的中间节点的方法,有一篇类似的可以参考LeetCode刷题【876】链表的中间结点,不过这边的双指针方法的话不方便往回查找节点。

    这边分奇偶长度,偶数长度直接和后一位对比,奇数长度中间节点不需要对比,拿中间节点的前一位和后一位开始对比。

    class Solution {
        public boolean isPalindrome(ListNode head) {
            if (head.next == null){
                return true;
            }
            List<ListNode> list = new ArrayList<>();
            while (head!=null){
                list.add(head);
                head = head.next;
            }
            int mid = list.size() >> 1;
            int l,r;
            if (list.size()%2==0){
                l = mid;
                r = mid+1;
            }else{
                l = mid;
                r = mid+2;
            }
            while (l>0){
                if (list.get(l-1).val != list.get(r-1).val){
                    return false;
                }
                l--;
                r++;
            }
            return true;
        }
    }

    而O(n) 时间复杂度和 O(1)的方法,一开始没想出来,直到看了别人的解法,重新再看到题面的说明,每个节点的值在0-9之间才豁然开朗。所以,题面中的每一个信息都至关重要。

    class Solution {
        public boolean isPalindrome(ListNode head) {
            int num1 = 0, num2 = 0, x = 1;
            while (head!=null){
                num1 = num1 * 10 + head.val;
                num2 = x * head.val + num2;
                x *= 10;
                head = head.next;
            }
            return num1 == num2;
        }
    }

    逻辑就是这样,构建两个值,不同的位上代表对应的节点的值,这样构建出来的整数和反向的位构建出来的整数应该值是相等的,则说明是回文链表。

    不过这里的另一个条件,链表长度为10的5次方的话,我也不确定会不会出现越界超出Integer.MAX_VALUE之后恰好和另一个值相等的情况。

    另外一个解法,用栈,将链表的前半段压入栈。从中间节点开始,依次和栈顶节点对比,如果都相同,说明是回文。

    而在用栈的方法1此之前,我也曾经想过,用两个指针,一个从中间节点往后遍历,一个用递归的方法从中间节点往前返回,这两个指针的值进行比较。但是题面中的链表长度为10的5次方使这种方法成为了不可能,这样的递归深度,必然会导致栈溢出。不过其本质思想和用栈来解决是相同的。

    LeetCode刷题【504】七进制数

    给定一个整数 num,将其转化为 7 进制,并以字符串形式输出。

     

    示例 1:

    输入: num = 100
    输出: "202"
    

    示例 2:

    输入: num = -7
    输出: "-10"
    

     

    提示:

    • -107 <= num <= 107
    Related Topics
  • 数学

  • 👍 95
  • 👎 0
  • 题解

    理解数字的本质,例如:我说我包里有11个苹果,那么就表明我的包里的苹果和我的手指头一一对应的话,还要多出来一个,这个数量可以写作10进制的“11”,也可以写作16进制的“B”,也可以写作二进制的“1011”,他们都表示这么多苹果的数量。

    那么接下来,入参是10进制的数字num,我们也一样在10进制上操作运算,相对应的每满7进1。那么自然的就需要对面num数字除以7,以及取余相关的操作,而除下来的结果依然还是10进制的数字,所以仍然需要除以7继续转换,直到最后没有了。

    又因为最终结果需要输出为字符串,所以需要字符串存储结果还有拼装

    class Solution {
        public String convertToBase7(int num) {
            if (num==0)return "0";
            String signBit = num < 0? "-":"";
            String numStr = "";
            num = num<0?-num:num;
            while (num != 0){
                numStr = (num % 7) + numStr ;
                num = num/7;
            }
            return signBit + numStr;
        }
    }

    提交了下,好像……有一点点点点慢!

    解答成功:
    执行耗时:9 ms,击败了10.25% 的Java用户
    内存消耗:36.6 MB,击败了8.28% 的Java用户

    其实看也知道了,大量的字符串拼接操作,去看下底层代码就知道了,确实比较费时费事。
    那。既然知道了,何不自己来一遍呢?那么,接下来下面这段代码!

    class Solution {
        public String convertToBase7(int num) {
            if (num==0)return "0";
            int signBit = 1;
            if (num < 0){
                signBit = -1;
            }
            char[] arr = new char[10];
            int index = 9;
            num = num<0?-num:num;
            while (num != 0){
                arr[index] = (char)(48+ (num % 7));
                num = num/7;
                index--;
            }
            int length = 10-index-1;
            if (signBit<0){
                length++;
                arr[index] = '-';
                index--;
            }
            char[] newArr = new char[length];
            System.arraycopy(arr,index+1,newArr,0,length);
            return new String(newArr);
        }
    }

    结果

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

    至于为啥arr数组是10位,题目中限定了num的范围 -10⁷ <= num <= 10⁷ ,简单算下就知道应该是150666343的7进制 9位数字,再额外加一位符号位的话就是10位了

    写完题解之后去评论区翻了下,看到java.lang.Integer#toString(int, int)这个方法,去看了下源码,好像基本一毛一样

    源码

    public static String toString(int i, int radix) {
            if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
                radix = 10;
    
            /* Use the faster version */
            if (radix == 10) {
                return toString(i);
            }
    
            char buf[] = new char[33];
            boolean negative = (i < 0);
            int charPos = 32;
    
            if (!negative) {
                i = -i;
            }
    
            while (i <= -radix) {
                buf[charPos--] = digits[-(i % radix)];
                i = i / radix;
            }
            buf[charPos] = digits[-i];
    
            if (negative) {
                buf[--charPos] = '-';
            }
    
            return new String(buf, charPos, (33 - charPos));
        }

    这里最后返回的new String的构造方法如下

    public String(char value[], int offset, int count) {
            if (offset < 0) {
                throw new StringIndexOutOfBoundsException(offset);
            }
            if (count <= 0) {
                if (count < 0) {
                    throw new StringIndexOutOfBoundsException(count);
                }
                if (offset <= value.length) {
                    this.value = "".value;
                    return;
                }
            }
            // Note: offset or count might be near -1>>>1.
            if (offset > value.length - count) {
                throw new StringIndexOutOfBoundsException(offset + count);
            }
            this.value = Arrays.copyOfRange(value, offset, offset+count);
        }

    而最终的Arrays.copyOfRange方法内容

    public static char[] copyOfRange(char[] original, int from, int to) {
    int newLength = to - from;
    if (newLength < 0)
    throw new IllegalArgumentException(from + " > " + to);
    char[] copy = new char[newLength];
    System.arraycopy(original, from, copy, 0,
    Math.min(original.length - from, newLength));
    return copy;
    }

    一样的也是用System.arraycopy把有效位的字符复制到一个新的数组中去了

    可把我牛逼坏了,让我叉会儿腰。以上3段代码均出自JDK1.8源码