LeetCode面试题 17.10. 主要元素/最优二叉树II(树形DP)/NC73数组中出现次数超过一半的数字/229. 求众数 II/NC134股票(无限次交易)/NC114旋转字符串

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode面试题 17.10. 主要元素/最优二叉树II(树形DP)/NC73数组中出现次数超过一半的数字/229. 求众数 II/NC134股票(无限次交易)/NC114旋转字符串相关的知识,希望对你有一定的参考价值。

面试题 17.10. 主要元素

2021.7.9每日一题

题目描述

数组中占比超过一半的元素称之为主要元素。给你一个 整数 数组,找出其中的主要元素。若没有,返回 -1 。
请设计时间复杂度为 O(N) 、空间复杂度为 O(1) 的解决方案。

 

示例 1:

输入:[1,2,5,9,5,9,5,5,5]
输出:5
示例 2:

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

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

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

思路

昨天刚又做了一次,摩尔投票法,这里没有说肯定有一个数目大于一半的数存在,所以需要选出候选人后再遍历一次判断是否满足条件

class Solution {
    public int majorityElement(int[] nums) {
        int candidate1 = nums[0];
        int count = 1;
        int n = nums.length;
        for(int i = 1; i < n; i++){
            if(count == 0)
                candidate1 = nums[i];
            if(candidate1 == nums[i])
                count++;
            else{
                count--;
            }
        }

        count = 0;
        for(int num : nums){
            if(candidate1 == num)
                count++;
            if(count > n / 2)
                return candidate1;
        }
        return -1;
    }
}

最优二叉树II

学习一下这个题,树形dp就做过一次

题目描述

链接:https://www.nowcoder.com/questionTerminal/0d939e874a004f449a370aca1346dd5c
来源:牛客网

小团有一个由N个节点组成的二叉树,每个节点有一个权值。定义二叉树每条边的开销为其两端节点权值的乘积,二叉树的总开销即每条边的开销之和。小团按照二叉树的中序遍历依次记录下每个节点的权值,即他记录下了N个数,第i个数表示位于中序遍历第i个位置的节点的权值。之后由于某种原因,小团遗忘了二叉树的具体结构。在所有可能的二叉树中,总开销最小的二叉树被称为最优二叉树。现在,小团请小美求出最优二叉树的总开销。

输入描述:
第一行输入一个整数N(1<=N<=300),表示二叉树的节点数。

第二行输入N个由空格隔开的整数,表示按中序遍历记录下的各个节点的权值,所有权值均为不超过1000的正整数。

输出描述:
输出一个整数,表示最优二叉树的总开销。

思路

树形dp
dp[i][j][k]表示以k为根节点,i ~ j为子树节点的最小开销
从节点left到right轮流当根节点,通过递归分治构建左右子树计算整体的开销。
记忆化递归,顺便学习一下这种输入数据的方法

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class Main {
    static int[][][] mem;
    static int[] weight;
    static int n;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine().trim());
        String[] strW = br.readLine().trim().split(" ");
        weight = new int[n];
        for(int i = 0; i < n; i++) weight[i] = Integer.parseInt(strW[i]);
        // mem[l][r][k]表示以weight[l:r]为子节点,以weight[k]为根节点的树开销
        mem = new int[n][n][n];
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++)
                for(int k = 0; k < n; k++) mem[i][j][k] = -1;
        }
        //初始根节点没有确定,所以这里根节点写-1,确定根节点的逻辑在递归里完成
        //一般情况下,是遍历数组,确定根节点,然后对每个根节点进行递归
        System.out.println(recur(0, n - 1, -1));
    }
    
    private static int recur(int left, int right, int root) {
        if(left > right) return 0;
        if(root >= 0 && mem[left][right][root] != -1) return mem[left][right][root];
        int cost = Integer.MAX_VALUE;
        // [left,right]中的元素轮流做根节点构建二叉树
        int leftCost = 0, rightCost = 0;
        for(int i = left; i <= right; i++){
            leftCost = recur(left, i - 1, i);      // 左子树开销
            rightCost = recur(i + 1, right, i);    // 右子树开销
            // root=-1时表示初始根节点还没有确定,不会有根节点连接左右子树的边
            cost = Math.min(cost, leftCost + rightCost + weight[i]*(root != -1? weight[root]: 0));
        }
        if(root >= 0) mem[left][right][root] = cost;
        return cost;
    }
}

NC73数组中出现次数超过一半的数字

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。你可以假设数组是非空的,并且给定的数组总是存在多数元素。1<=数组长度<=50000
示例1
输入:
[1,2,3,2,2,2,5,4,2]

返回值:
2

思路

就是相互抵消,如果不一样就相互抵消,一样就增加数量,因为有一个数出现次数超过一半,所以最后剩下的结果肯定是这个数
原来叫摩尔投票法,这里最后不需要再检查一遍,是因为已经规定了肯定共有数超过一半,一般需要最后在检查一遍找出来的数是否是最多的数

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        //这个叫个什么方法来着,好像是博弈论的东西,就是相互抵消
        int l = array.length;
        int temp = array[0];
        int count = 1;
        for(int i = 1; i < l; i++){
            if(temp == array[i])
                count++;
            else{
                count--;
                if(count == 0)
                    temp = array[i + 1];
            }
        }
        return temp;
    }
}

再回顾一下选两个候选人的那道题:

229. 求众数 II

题目描述

给定一个大小为 n 的整数数组,找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素。

进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1)的算法解决此问题。

 	
示例 1:

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

输入:nums = [1]
输出:[1]
示例 3:

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

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

思路

还是摩尔投票法,这里因为找的是超过1/3的元素,所以这里定两个候选人,如果投票结果与这两个候选人其中一个相同,那么该候选人票数加1,如果都不相同,那么两个候选人的票数都减1,如果有一个等于0了,就更换候选人。因为这里没有说最后结果肯定有两个,所以最后需要验证,所得到的这两个候选人是否票数都超过了1/3

class Solution {
    public List<Integer> majorityElement(int[] nums) {
        //摩尔投票法(升级),我理解这个方法就是,选n个候选人,就需要有多于 1 / (n + 1)的票数,
        //所以将剩下的小于1/n的人和这几个候选人每个人的票数相加,就相当于每个候选人的票数大于这一部分的1/2,
        //所以就可以转换为169题,选大于1/2的那个题
        //所以先确定两个候选人,如果第三个投票结果和他们中任何一个相同,使其中那个人的票数加1;
        //如果第三个投票结果和他们任何一个都不同,那么就都抵消掉
        //如果某一个计数等于0,那就更换候选人
        //以这个过程遍历结束,剩余结果可能是符合要求的,也可能是不符合的
        //所以需要计数阶段,就是遍历一次nums,统计个数

        List<Integer> res = new ArrayList<>();
        int cand1 = nums[0], count1 = 0;
        //这里两个候选人先赋值为一样的也没关系,因为后面抵消阶段的条件是continue
        int cand2 = nums[0], count2 = 0;
        //抵消阶段
        for(int n : nums){
            if(n == cand1){
                count1++;
                continue;
            }
            if(n == cand2){
                count2++;
                continue;
            }
            //当count1等于0的时候,换候选人
            if(count1 == 0){
                cand1 = n;
                count1++;
                continue;
            }
            if(count2 == 0){
                cand2 = n;
                count2++;
                continue;
            }
            //如果不相同,就减去
            count1--;
            count2--;
        }
        //计数阶段
        count1 = 0;
        count2 = 0;
        for(int n : nums){
            if(cand1 == n)
                count1++;
            //这里必须用else if,因为两个候选人可能相同
            else if(cand2 == n){
                count2++;
            }
        }
        if(count1 > nums.length / 3)
            res.add(cand1);
        if(count2 > nums.length / 3)
            res.add(cand2);
        return res;
    }
}

NC134股票(无限次交易)

题目描述

假定你知道某只股票每一天价格的变动。
你最多可以同时持有一只股票。但你可以无限次的交易(买进和卖出均无手续费)。
请设计一个函数,计算你所能获得的最大收益。
示例1
输入:
[5,4,3,2,1]

返回值:
0
说明:
由于每天股票都在跌,因此不进行任何交易最优。最大收益为0。

思路

无限次就是贪心

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 计算最大收益
     * @param prices int整型一维数组 股票每一天的价格
     * @return int整型
     */
    public int maxProfit (int[] prices) {
        // write code here
        int l = prices.length;
        int profit = 0;
        for(int i = 1; i < l; i++){
            if(prices[i] > prices[i - 1])
                profit += prices[i] - prices[i - 1];
        }
        return profit;
    }
}

NC114旋转字符串

import java.util.*;


public class Solution {
    /**
     * 旋转字符串
     * @param A string字符串 
     * @param B string字符串 
     * @return bool布尔型
     */
    public boolean solve (String A, String B) {
        // write code here
        //想了一下,旋转和切割然后拼接感觉差不多复杂度
        int la = A.length();
        int lb = B.length();
        if(la != lb)
            return false;
        for(int i = 0; i < la; i++){
            String sub1 = A.substring(0, i + 1);
            String sub2 = A.substring(i + 1, la);
            if(B.equals(sub2 + sub1))
                return true;
        }
        return false;
    }
}

将两个A拼接,然后找B,调用API的方法

import java.util.*;


public class Solution {
    /**
     * 旋转字符串
     * @param A string字符串 
     * @param B string字符串 
     * @return bool布尔型
     */
    public boolean solve (String A, String B) {
        // write code here
        //给的方法,因为是左右互换,所以将两个A拼接,中间的字符串就是拼接的结果
        //然后在AA这个字符串中找B就行了
        return A.length() != B.length() ? false : (A + A).contains(B);
    }
}

写个KMP找B的方法,自己能直接写出来了,说明这个点也掌握了

import java.util.*;


public class Solution {
    /**
     * 旋转字符串
     * @param A string字符串 
     * @param B string字符串 
     * @return bool布尔型
     */
    public boolean solve (String A, String B) {
        // write code here
        //给的方法,因为是左右互换,所以将两个A拼接,中间的字符串就是拼接的结果
        //然后在AA这个字符串中找B就行了
        return A.length() != B.length() ? false : KMP(A + A, B);
    }
    public boolean KMP(String s, String sub){
        int[] next = getNext(sub);
        int i = 0;
        int j = 0;
        int l = s.length();
        while(i < l){
            while(j > 0 && s.charAt(i) != sub.charAt(j))
                j = next[j - 1];
            if(s.charAt(i) == sub.charAt(j))
                j++;
            if(j == sub.length())
                return true;
            i++;
        }
        return false;
    }
    
    public int[] getNext(String s){
        int l = s.length();
        char[] cc = s.toCharArray();
        int[] next = new int[l];
        int i = 1;
        int j = 0;
        while(i < l){
            while(j > 0 && cc[j] != cc[i])
                j = next[j - 1];
            if(cc[j] == cc[i])
                j++;
            next[i] = j;
            i++;
        }
        return next;
    }
}

以上是关于LeetCode面试题 17.10. 主要元素/最优二叉树II(树形DP)/NC73数组中出现次数超过一半的数字/229. 求众数 II/NC134股票(无限次交易)/NC114旋转字符串的主要内容,如果未能解决你的问题,请参考以下文章

文巾解题 面试题 17.10. 主要元素

面试题 17.10. 主要元素投票算法 C++

LeetCode 面试题 17.10 主要元素[摩尔投票法] HERODING的LeetCode之路

LeetCode面试题17.18 最短超串(典型滑动窗口)

LeetCode 面试题 02.01. 移除重复节点

leetcode 最常见的 150 道前端面试题(简单题上)