LeetCode 483. 最小好进制(数学) / 1239. 串联字符串的最大长度 / 1600. 皇位继承顺序(多叉树) / 401. 二进制手表

483. 最小好进制

2021.6.18每日一题

题目描述
对于给定的整数 n, 如果n的k(k>=2)进制数的所有数位全为1,则称 k(k>=2)是 n 的一个好进制。

以字符串的形式给出 n, 以字符串的形式返回 n 的最小好进制。


示例 1:

输入:"13"
输出:"3"
解释:13 的 3 进制是 111。
示例 2:

输入:"4681"
输出:"8"
解释:4681 的 8 进制是 11111。
示例 3:

输入:"1000000000000000000"
输出:"999999999999999999"
解释:1000000000000000000 的 999999999999999999 进制是 11。

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

思路

看了标签,是二分,但是还是没想到怎么二分,遍历的话肯定会超时
官解的方法,可能前面的等比数列还能想到,但是后面的放缩确实不太好想

假设正整数 n在k (k≥2) 进制下的所有数位都为 1,且位数为 m + 1,那么有:
n = k ^ 0 + k ^ 1 + … + k ^ m
当m == 1 时,n = (11)(k进制下),肯定符合要求
可以看到这是一个等比数列,根据等比数列的求和公式,可以有以下结果
LeetCode 483. 最小好进制(数学) / 1239. 串联字符串的最大长度 / 1600. 皇位继承顺序(多叉树) / 401. 二进制手表_第1张图片
这里确定了m的范围
接下来确定k进行放缩,主要是用了二项式定理,可以直接确定k的一个不超过1的大小范围

也看了各种写二分的方法,但是多是python写的,因为二分主要是用在第二步,如果没有放缩的话,k需要二分来求,因为k可能会很大,检查函数中得到的结果可能会更大,python数可以取到无限大,但是java中二分会超出数据范围。

class Solution {
    //官解的方法
    public String smallestGoodBase(String n) {
        long nVal = Long.parseLong(n);      //转换成long
        //假设正整数 n 在 k(k≥2) 进制下的所有数位都为 1,且位数为 m + 1,那么有:
        //由等比数列的前n项和公式,求出m的最大值
        int mMax = (int) Math.floor(Math.log(nVal) / Math.log(2));
        //m由大到小遍历
        for (int m = mMax; m > 1; m--) {
            //根据二项式定理确定k的范围
            //求出此时对应的k(k可能是整数或者小数)
            int k = (int) Math.pow(nVal, 1.0 / m);
            long mul = 1, sum = 1;
            //计算当前进制下,这个数应该是多少
            for (int i = 0; i < m; i++) {
                mul *= k;
                sum += mul;
            }
            //如果这个数和所给数相等,就返回这个进制
            if (sum == nVal) {
                return Integer.toString(k);
            }
        }
        //否则返回最大的进制数n - 1
        return Long.toString(nVal - 1);
    }
}

1239. 串联字符串的最大长度

2021.6.19每日一题

题目描述
给定一个字符串数组 arr,字符串 s 是将 arr 某一子序列字符串连接所得的字符串,如果 s 中的每一个字符都只出现过一次,那么它就是一个可行解。

请返回所有可行解 s 中最长长度。

示例 1:

输入:arr = ["un","iq","ue"]
输出:4
解释:所有可能的串联组合是 "","un","iq","ue","uniq" 和 "ique",最大长度为 4。
示例 2:

输入:arr = ["cha","r","act","ers"]
输出:6
解释:可能的解答有 "chaers" 和 "acters"。
示例 3:

输入:arr = ["abcdefghijklmnopqrstuvwxyz"]
输出:26

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

思路

我的思路呢,就是暴力回溯,遍历所有可行的情况,细节需要好好处理,超一半

class Solution {
    List<String> arr;
    int max = 0;
    public int maxLength(List<String> arr) {
        //数据范围不大,可以暴力
        int[] count = new int[26];
        this.arr = arr;
        backtracking(count, 0, 0);
        return max;
    }

    public void backtracking(int[] count, int index, int res){
    	//如果到最后了
        if(index == arr.size()){
            max = Math.max(max, res);
            return;
        }
        for(int i = index; i < arr.size(); i++){
            String cur = arr.get(i);
            //如果当前字符串长度和结果字符串长度之和大于26了,不可行
            if(res + cur.length() > 26)
                continue;
            //用于判断当前字符串是否符合条件
            int[] temp = new int[26];
            boolean flag = false;
            for(int j = 0; j < cur.length(); j++){
                char c = cur.charAt(j);
                if(count[c - 'a'] == 1 || temp[c - 'a'] == 1){
                    flag = true;
                    break;
                }
                temp[c - 'a']++;
            }
            //如果不符合,跳过
            if(flag)
                continue;
            //如果能走到这里,说明这个字符串是合适的
            max = Math.max(max, res + cur.length());
            for(int j = 0; j < cur.length(); j++){
                char c = cur.charAt(j);
                count[c - 'a']++;
            }
            backtracking(count, i + 1, res + cur.length());
            //回溯
            for(int j = 0; j < cur.length(); j++){
                char c = cur.charAt(j);
                count[c - 'a']--;
            }
        }
    }   
}

可以先预处理arr集合,将符合条件的字符串挑选出来,并用二进制的形式表示

class Solution {
    int ans = 0;

    public int maxLength(List<String> arr) {
        List<Integer> masks = new ArrayList<Integer>();
        //预处理每个字符串,将符合条件的字符串用二进制表示
        for (String s : arr) {
            int mask = 0;
            for (int i = 0; i < s.length(); ++i) {
                int ch = s.charAt(i) - 'a';
                if (((mask >> ch) & 1) != 0) { // 若 mask 已有 ch,则说明 s 含有重复字母,无法构成可行解
                    mask = 0;
                    break;
                }
                mask |= 1 << ch; // 将 ch 加入 mask 中
            }
            if (mask > 0) {
                masks.add(mask);
            }
        }

        backtrack(masks, 0, 0);
        return ans;
    }

    public void backtrack(List<Integer> masks, int pos, int mask) {
    	//如果遍历到最后了,统计结果
        if (pos == masks.size()) {
            ans = Math.max(ans, Integer.bitCount(mask));
            return;
        }
        //如果当前字符串和结果字符串没有重复的字母,就添加这个字符串
        if ((mask & masks.get(pos)) == 0) { // mask 和 masks[pos] 无公共元素
            backtrack(masks, pos + 1, mask | masks.get(pos));
        }
        //否则,跳过这个字符串
        backtrack(masks, pos + 1, mask);
    }
}

1600. 皇位继承顺序

2021.6.21每日一题

题目描述
一个王国里住着国王、他的孩子们、他的孙子们等等。每一个时间点,这个家庭里有人出生也有人死亡。

这个王国有一个明确规定的皇位继承顺序,第一继承人总是国王自己。我们定义递归函数 Successor(x, curOrder) ,给定一个人 x 和当前的继承顺序,该函数返回 x 的下一继承人。

Successor(x, curOrder):
    如果 x 没有孩子或者所有 x 的孩子都在 curOrder 中:
        如果 x 是国王,那么返回 null
        否则,返回 Successor(x 的父亲, curOrder)
    否则,返回 x 不在 curOrder 中最年长的孩子
比方说,假设王国由国王,他的孩子 Alice 和 Bob (Alice 比 Bob 年长)和 Alice 的孩子 Jack 组成。

一开始, curOrder 为 ["king"].
调用 Successor(king, curOrder) ,返回 Alice ,所以我们将 Alice 放入 curOrder 中,得到 ["king", "Alice"] 。
调用 Successor(Alice, curOrder) ,返回 Jack ,所以我们将 Jack 放入 curOrder 中,得到 ["king", "Alice", "Jack"] 。
调用 Successor(Jack, curOrder) ,返回 Bob ,所以我们将 Bob 放入 curOrder 中,得到 ["king", "Alice", "Jack", "Bob"] 。
调用 Successor(Bob, curOrder) ,返回 null 。最终得到继承顺序为 ["king", "Alice", "Jack", "Bob"] 。
通过以上的函数,我们总是能得到一个唯一的继承顺序。

请你实现 ThroneInheritance 类:

ThroneInheritance(string kingName) 初始化一个 ThroneInheritance 类的对象。国王的名字作为构造函数的参数传入。
void birth(string parentName, string childName) 表示 parentName 新拥有了一个名为 childName 的孩子。
void death(string name) 表示名为 name 的人死亡。一个人的死亡不会影响 Successor 函数,也不会影响当前的继承顺序。你可以只将这个人标记为死亡状态。
string[] getInheritanceOrder() 返回 除去 死亡人员的当前继承顺序列表。
 

示例:

输入:
["ThroneInheritance", "birth", "birth", "birth", "birth", "birth", "birth", "getInheritanceOrder", "death", "getInheritanceOrder"]
[["king"], ["king", "andy"], ["king", "bob"], ["king", "catherine"], ["andy", "matthew"], ["bob", "alex"], ["bob", "asha"], [null], ["bob"], [null]]
输出:
[null, null, null, null, null, null, null, ["king", "andy", "matthew", "bob", "alex", "asha", "catherine"], null, ["king", "andy", "matthew", "alex", "asha", "catherine"]]

解释:
ThroneInheritance t= new ThroneInheritance("king"); // 继承顺序:king
t.birth("king", "andy"); // 继承顺序:king > andy
t.birth("king", "bob"); // 继承顺序:king > andy > bob
t.birth("king", "catherine"); // 继承顺序:king > andy > bob > catherine
t.birth("andy", "matthew"); // 继承顺序:king > andy > matthew > bob > catherine
t.birth("bob", "alex"); // 继承顺序:king > andy > matthew > bob > alex > catherine
t.birth("bob", "asha"); // 继承顺序:king > andy > matthew > bob > alex > asha > catherine
t.getInheritanceOrder(); // 返回 ["king", "andy", "matthew", "bob", "alex", "asha", "catherine"]
t.death("bob"); // 继承顺序:king > andy > matthew > bob(已经去世)> alex > asha > catherine
t.getInheritanceOrder(); // 返回 ["king", "andy", "matthew", "alex", "asha", "catherine"]

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

思路

说实话,看题的时候,真没看懂在说啥,不知道那个Successor函数到底能不能用
然后思路就是创建一个树吧,谁生了孩子,就在那个人对应的节点增加一个子节点,代表自己的孩子
然后死了就把这个节点的一个属性变成其他值,
继承顺序的列表,就是遍历这颗树吧,根左右,应该是前序遍历,对于已经死亡的结点,不进行输出

class ThroneInheritance {
    //写一下多叉树的实现,不同于二叉树,多叉树的子节点个数是不确定的
    //父节点与子节点的联系
    Map<String, List<String>> edges;
    String king;
    //死亡的节点
    Set<String> dead;

    public ThroneInheritance(String kingName) {
        edges = new HashMap<>();;
        king = kingName;
        //死亡的节点
        dead = new HashSet<>();
    }
    
    public void birth(String parentName, String childName) {
        //先得到父亲对应的孩子集合
        List<String> list = edges.getOrDefault(parentName, new ArrayList<String>());
        list.add(childName);
        //重新放入edges集合
        edges.put(parentName, list);
    }
    
    public void death(String name) {
        dead.add(name);
    }
    
    public List<String> getInheritanceOrder() {
        //前序遍历
        List<String> res = new ArrayList<>();
        preorder(king, res);
        return res;
    }

    public void preorder(String name, List<String> res){
        //如果没有死,加入集合;如何死了,也需要统计他的孩子
        if(!dead.contains(name))
            res.add(name);
        
        List<String> child = edges.getOrDefault(name, new ArrayList<String>());
        //挨个遍历子树
        for(String s : child){
            preorder(s, res);
        }
    }
}

/**
 * Your ThroneInheritance object will be instantiated and called as such:
 * ThroneInheritance obj = new ThroneInheritance(kingName);
 * obj.birth(parentName,childName);
 * obj.death(name);
 * List param_3 = obj.getInheritanceOrder();
 */

三叶姐的单向链表,tql

class ThroneInheritance {
	//一个节点表示一个人
    class Node {
        String name;
        Node next;
        Node last; // 记录作为父亲的最后一个儿子
        boolean isDeleted = false;
        Node (String _name) {
            name = _name;
        }
    }
    
    Map<String, Node> map = new HashMap<>();
    //虚拟头节点和尾节点
    Node head = new Node(""), tail = new Node("");
    public ThroneInheritance(String name) {
        Node root = new Node(name);
        root.next = tail;
        head.next = root;
        map.put(name, root);
    }
    //出生
    public void birth(String pname, String cname) {
        //先创建这个节点
        Node node = new Node(cname);
		//放在哈希表中,方便查找
        map.put(cname, node);
        //找到父节点
        Node p = map.get(pname);
        Node tmp = p;
        //找到父节点的最后一个孩子的孩子
        while (tmp.last != null) tmp = tmp.last;
		//把这个孩子插到最后一个孩子的孩子的后面
        node.next = tmp.next;
        tmp.next = node;
        //更改父节点的最后一个孩子
        p.last = node;
    }
    //死亡,直接置为true
    public void death(String name) {
        Node node = map.get(name);
        node.isDeleted = true;
    }
    //遍历这个链表,就是继承顺序
    public List<String> getInheritanceOrder() {
        List<String> ans = new ArrayList<>();
        Node tmp = head.next;
        while (tmp.next != null) {
            if (!tmp.isDeleted) ans.add(tmp.name);
            tmp = tmp.next;
        }
        return ans;
    }
}

作者:AC_OIer
链接:https://leetcode-cn.com/problems/throne-inheritance/solution/gong-shui-san-xie-shi-yong-dan-xiang-lia-7t65/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

突然看到一个多叉树变成二叉树的题解
怎么变呢,一个树节点P的左子节点表示P的孩子,而P的右子节点表示P的兄弟。
也就是说,对于一个节点,生了一个孩子,如果此时它还没有孩子,那么就插入P的左子结点,如果有了孩子,就插入P的左子节点的右子节点方向(相当于都是好兄弟)
最后继承顺序,还是前序遍历

class ThroneInheritance {
    HashMap<String,TreeNode> map = new HashMap<>();
    TreeNode root;
    public ThroneInheritance(String kingName) {
        root = new TreeNode(kingName);
        map.put(kingName,root);
    }

    //先找到父亲节点,如果是第一个孩子,则插入左节点,如果是其他孩子则插入左节点的最右节点
    public void birth(String parentName, String childName) {
        TreeNode parent = map.get(parentName);
        TreeNode child = new TreeNode(childName);
        map.put(childName,child);
        if(parent.left == null){
            parent.left = child;
        }else{
            TreeNode cur = parent.left;
            while(cur.right != null){
                cur = cur.right;
            }
            cur.right = child;
        }
    }

    //找到该节点,标记为死亡
    public void death(String name) {
        TreeNode people = map.get(name);
        people.state = 1;
    }

    //前序遍历
    public List<String> getInheritanceOrder() {
        List<String> order = new ArrayList<>();
        visit(root,order);
        return order;
    }

    //辅助的继承顺序遍历函数,实际上就是一个前序遍历
    private void visit(TreeNode t,List<String> order){
        if(t != null){
            if (t.state == 0){
                order.add(t.name);
            }
            visit(t.left,order);
            visit(t.right,order);
        }
    }
}

//定义树节点
class TreeNode{
    String name;
    int state;
    TreeNode left;
    TreeNode right;
    public TreeNode(String name){
        this.name = name;
        state = 0;
    }
}

/**
 * Your ThroneInheritance object will be instantiated and called as such:
 * ThroneInheritance obj = new ThroneInheritance(kingName);
 * obj.birth(parentName,childName);
 * obj.death(name);
 * List param_3 = obj.getInheritanceOrder();
 */

作者:bob_king_wei
链接:https://leetcode-cn.com/problems/throne-inheritance/solution/duo-cha-shu-bian-er-cha-shu-qian-xu-bian-qp1u/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

做完这个题我突然想到一个问题,皇位继承确实是按这个顺序继承,还是如果太子死了,就是太子的兄弟继承,而不管太子有没有孩子。
最后呢,查了一下,发现还真是按这个题所给的这种逻辑继承,哈哈哈

401. 二进制手表

6.21每日一题

题目描述
二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。每个 LED 代表一个 0 或 1,最低位在右侧。
给你一个整数 turnedOn ,表示当前亮着的 LED 的数量,返回二进制手表可以表示的所有可能时间。你可以 按任意顺序 返回答案。

小时不会以零开头:

例如,"01:00" 是无效的时间,正确的写法应该是 "1:00" 。
分钟必须由两位数组成,可能会以零开头:

例如,"10:2" 是无效的时间,正确的写法应该是 "10:02" 。
 
示例 1:

输入:turnedOn = 1
输出:["0:01","0:02","0:04","0:08","0:16","0:32","1:00","2:00","4:00","8:00"]
示例 2:

输入:turnedOn = 9
输出:[]

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

思路

我的思路是分成小时部分和分钟部分,小时部分有 i 位的话,分钟部分就有turnedOn - i 位,然后回溯,枚举所有情况,加入集合中
过了,超50%

class Solution {
    public List<String> readBinaryWatch(int turnedOn) {
        //就是枚举呗
        List<String> res = new ArrayList<>();
        if(turnedOn >= 9)
            return res;
        
        //在小时里面最多选3个,在分钟里面最多选5个
        int hmax = Math.min(turnedOn, 3);
        //选i位当做小时
        for(int i = 0; i <= hmax; i++){
            List<String> h = new ArrayList<>();
            List<String> min = new ArrayList<>();
            selectHour(i, 0, 0, h);
            selectMinute(turnedOn - i, 0, 0, min);
            int l = h.size();
            while(l-- > 0){
                //取出这个删除的数
                String hour = h.remove(0);
                for(int j = 0; j < min.size(); j++){
                    h.add(hour + ":" + min.get(j));
                }
            }
            res.addAll(h);
        }
        return res;
    }

    //4位里面选count位
    public void selectHour(int count, int cur, int index, List<String> h){
        if(cur > 11)
            return;
        if(count == 0){
            h.add(String.valueOf(cur));
            return;
        }
        if(index == 4)
            return;

        for(int i = index; i < 4; i++){
            selectHour(count - 1, cur + (1 << i), i + 1, h);
        }
    }

    //6位里面选count位
    public void selectMinute(int count, int cur, int index, List<String> min){
        if(cur > 59)
            return;
        if(count == 0){
            if(cur <= 9)
                min.add("0" + String.valueOf(cur));
            else
                min.add(String.valueOf(cur));
            return;
        }
        if(index == 6)
            return;

        for(int i = index; i < 6; i++){
            selectMinute(count - 1, cur + (1 << i), i + 1, min);
        }
    }

}

但是这样写有点费时间,而且有些细节也比较麻烦,简单题应该不至于这样,所以看了一下题解,发现直接枚举时分,或者二进制枚举就可以了,这样代码编写简单点,如果是比赛的话比较节省时间

//枚举时分
class Solution {
    public List<String> readBinaryWatch(int turnedOn) {
        List<String> ans = new ArrayList<String>();
        for (int h = 0; h < 12; ++h) {
            for (int m = 0; m < 60; ++m) {
                if (Integer.bitCount(h) + Integer.bitCount(m) == turnedOn) {
                    ans.add(h + ":" + (m < 10 ? "0" : "") + m);
                }
            }
        }
        return ans;
    }
}

//二进制枚举,总共10位,前4位表示小时,后6位表示分钟
class Solution {
    public List<String> readBinaryWatch(int turnedOn) {
        List<String> ans = new ArrayList<String>();
        for (int i = 0; i < 1024; ++i) {
            int h = i >> 6, m = i & 63; // 用位运算取出高 4 位和低 6 位
            if (h < 12 && m < 60 && Integer.bitCount(i) == turnedOn) {
                ans.add(h + ":" + (m < 10 ? "0" : "") + m);
            }
        }
        return ans;
    }
}

三叶姐的打表,第一次见做题用静态代码块的(静态代码块在类加载时执行),学习一下

class Solution {    
    // 打表逻辑,也可以放到本地做
    // 注意使用 static 修饰,确保打表数据只会被生成一次
    static Map<Integer, List<String>> map = new HashMap<>();
    static {
        for (int h = 0; h <= 11; h++) {
            for (int m = 0; m <= 59; m++) {
                int tot = getCnt(h) + getCnt(m);
                List<String> list = map.getOrDefault(tot, new ArrayList<String>());
                list.add(h + ":" + (m <= 9 ? "0" + m : m));
                map.put(tot, list);
            }
        }
    }
    //统计x中1的个数
    static int getCnt(int x) {
        int ans = 0;
        //每次减去最低位的1
        for (int i = x; i > 0; i -= lowbit(i)) ans++;
        return ans;
    }
    static int lowbit(int x) {
        return x & -x;
    }
    public List<String> readBinaryWatch(int t) {
        return map.getOrDefault(t, new ArrayList<>());
    }
}

作者:AC_OIer
链接:https://leetcode-cn.com/problems/binary-watch/solution/gong-shui-san-xie-jian-dan-ti-xue-da-bia-gwn2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的:(LeetCode,leetcode,java)