LeetCode 451. 根据字符出现频率排序 / 645. 错误的集合 / 726. 原子的数量 / NC52 括号序列 / NC102 最近公共祖先 / NC78 反转链表

451. 根据字符出现频率排序

2021.7.3 每日一题

题目描述
给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

示例 1:

输入:
"tree"

输出:
"eert"

解释:
'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。
示例 2:

输入:
"cccaaa"

输出:
"cccaaa"

解释:
'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。
示例 3:

输入:
"Aabb"

输出:
"bbAa"

解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。

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

思路

我的思路就是哈希表存储出现的次数,然后在优先队列里存储这样的键值对,然后按出现次数排序

class Solution {
    public String frequencySort(String s) {
        //排序问题
        Map<Character, Integer> map = new HashMap<>();
        int l = s.length();
        for(int i = 0; i < l; i++){
            char c = s.charAt(i);
            map.put(c, map.getOrDefault(c, 0) + 1);
        }
        PriorityQueue<Node> pq = new PriorityQueue<>(new Comparator<Node>(){
            public int compare(Node a, Node b){
                return b.t - a.t;
            }
        });

        for(Map.Entry<Character, Integer> entry : map.entrySet()){
            char key = entry.getKey();
            int value = entry.getValue();
            pq.offer(new Node(key, value));
        } 

        StringBuffer sb = new StringBuffer(l);
        while(!pq.isEmpty()){
            Node node = pq.poll();
            for(int i = 0; i < node.t; i++){
                sb.append(node.c);
            }
        }
        return sb.toString();

    }
}

class Node{
    char c;
    int t;
    public Node(char c, int t){
        this.c = c;
        this.t = t;
    }
}

学习一下正则表达式的排序写法

		PriorityQueue<Node> q = new PriorityQueue<>((a,b)->{
            if (b.v != a.v) return b.v - a.v;
            return a.c - b.c;
        });

或者存储到list中

		List<Character> list = new ArrayList<Character>(map.keySet());
        Collections.sort(list, (a, b) -> map.get(b) - map.get(a));

运用一下桶排序,顺便复习一下
就是创建出现次数的桶,然后根据字符出现的次数将字符放进对应的桶中
然后从后向前遍历桶,取出桶中的字符,拼接起来就可以了
这里因为每个桶里面可能有多个字符,所以用了StringBuffer当桶,并且因为拼接的顺序无所谓,所以这里按照出现先后顺序拼接可行

class Solution {
    public String frequencySort(String s) {
        Map<Character, Integer> map = new HashMap<Character, Integer>();
        int maxFreq = 0;    //最大出现频率
        int length = s.length();
        for (int i = 0; i < length; i++) {
            char c = s.charAt(i);
            int frequency = map.getOrDefault(c, 0) + 1;
            map.put(c, frequency);
            maxFreq = Math.max(maxFreq, frequency);
        }
        //创建max个桶,每个桶中存放出现max次的字符
        StringBuffer[] buckets = new StringBuffer[maxFreq + 1];
        for (int i = 0; i <= maxFreq; i++) {
            buckets[i] = new StringBuffer();
        }
        //将哈希表中的数据放入桶中
        for (Map.Entry<Character, Integer> entry : map.entrySet()) {
            char c = entry.getKey();
            int frequency = entry.getValue();
            buckets[frequency].append(c);
        }
        StringBuffer sb = new StringBuffer();
        //反向遍历桶中的字符,并进行拼接
        for (int i = maxFreq; i > 0; i--) {
            StringBuffer bucket = buckets[i];
            int size = bucket.length();
            for (int j = 0; j < size; j++) {
                for (int k = 0; k < i; k++) {
                    sb.append(bucket.charAt(j));
                }
            }
        }
        return sb.toString();
    }
}

或者三叶姐这种直接用数组排序的思路

class Solution {   
    public String frequencySort(String s) {
	    //二维数组,第一维是字符,第二维两个数,第一个表示出现下标,也就是是哪个字符,第二个表示这个字符出现的次数
        int[][] cnts = new int[128][2];
        char[] cs = s.toCharArray();
        for (int i = 0; i < 128; i++) cnts[i][0] = i;
        for (char c : cs) cnts[c][1]++;
        //从大到小排序
        Arrays.sort(cnts, (a, b)->{
            if (a[1] != b[1]) return b[1] - a[1];
            return a[0] - b[0];
        });
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 128; i++) {
            char c = (char)cnts[i][0];
            int k = cnts[i][1];
            while (k-- > 0) sb.append(c);
        }
        return sb.toString();
    }
}

作者:AC_OIer
链接:https://leetcode-cn.com/problems/sort-characters-by-frequency/solution/gong-shui-san-xie-shu-ju-jie-gou-yun-yon-gst9/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

645. 错误的集合

2021.7.5 每日一题

题目描述
集合 s 包含从 1 到 n 的整数。不幸的是,因为数据错误,导致集合里面某一个数字复制了成了集合里面的另外一个数字的值,导致集合 丢失了一个数字 并且 有一个数字重复 。

给定一个数组 nums 代表了集合 S 发生错误后的结果。

请你找出重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。

 

示例 1:

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

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

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

思路

直接遍历计数

class Solution {
    public int[] findErrorNums(int[] nums) {
        int l = nums.length;
        int[] count = new int[l + 1];
        int more = 0;
        int loss = 0;
        for(int num : nums){
            count[num]++;
            if(count[num] == 2)
                more = num;
        }
        for(int i = 1; i <= l; i++){
            if(count[i] == 0)
                loss = i;
        }
        return new int[]{more, loss};
    }
}

或者官解中的思路,把这个题变成另一道题:在一个数组中每一个数都出现两次,除了两个数字出现一次,找出这两个数字

class Solution {
    public int[] findErrorNums(int[] nums) {
        //官解的位运算很巧妙,把这个问题转变成了另一个问题
        //一个数组中所有数字都出现两次,只有两个数字出现一次
        //怎么转变呢,就是添加1-n个数,那么所有数字出现了两次,一个出现了3次,一个出现了一次
        //怎么找这两个数字呢,就是将所有数字异或,也就是这两个数字异或,求出最低的不同位。
        //然后根据这个不同位,将数组分成两份。
        //然后再对两个不同的数组异或,分别得到的数字就是两个要找的数
        //最后重新遍历一遍数组,找到这两个数字分别是多的还是缺失的就可以了
        int n = nums.length;
        int xor = 0;
        for (int num : nums) {
            xor ^= num;
        }
        //相当于拼接1-n这个数组
        for (int i = 1; i <= n; i++) {
            xor ^= i;
        }
        //取出异或结果的最低位
        int lowbit = xor & (-xor);
        //根据这个lowbit将数组分成两部分,然后将两个子数组分别异或
        int num1 = 0, num2 = 0;
        for (int num : nums) {
            if ((num & lowbit) == 0) {
                num1 ^= num;
            } else {
                num2 ^= num;
            }
        }
        for (int i = 1; i <= n; i++) {
            if ((i & lowbit) == 0) {
                num1 ^= i;
            } else {
                num2 ^= i;
            }
        }
        //找出两个数以后,分不清哪个是缺失的
        //在原数组中找,如果原数组中有num1,那么输出num1在前,否则num2在前             
        for (int num : nums) {
            if (num == num1) {
                return new int[]{num1, num2};
            }
        }
        return new int[]{num2, num1};
    }
}

第二个思路,利用求和公式,相当于数学题一样解,就不写了
第三个思路,桶排序,因为是1-n,所以将每个数字放到对应的位置n-1,只有一个位置放的是重复的数字,最后遍历一次将这个数字取出

class Solution {
    public int[] findErrorNums(int[] nums) {
        int n = nums.length;
        for(int i = 0; i < n; i++){
            //第i个位置应该放i+1,如果不是,将nums[i]放在对应的位置
            while(nums[i] != i + 1){
                //如果对应位置上已经是这个值了,那么不论怎么交换,i位置也不可能有对应的值,所以跳出
                if(nums[nums[i] - 1] == nums[i])
                    break;
                int t = nums[i];
                nums[i] = nums[t - 1];
                nums[t - 1] = t;
            }
        }
        //这时,每个位置上都是对应的数,除了缺失位置上是多余的数
        for(int i = 0; i < n; i++){
            if(nums[i] != i + 1)
                return new int[]{nums[i], i + 1};
        }
        return new int[]{};
    }
}

726. 原子的数量

2021.7.5 每日一题

题目描述
给定一个化学式formula(作为字符串),返回每种原子的数量。

原子总是以一个大写字母开始,接着跟随0个或任意个小写字母,表示原子的名字。

如果数量大于 1,原子后会跟着数字表示原子的数量。如果数量等于 1 则不会跟数字。例如,H2O 和 H2O2 是可行的,但 H1O2 这个表达是不可行的。

两个化学式连在一起是新的化学式。例如 H2O2He3Mg4 也是化学式。

一个括号中的化学式和数字(可选择性添加)也是化学式。例如 (H2O2) 和 (H2O2)3 是化学式。

给定一个化学式,输出所有原子的数量。格式为:第一个(按字典序)原子的名子,跟着它的数量(如果数量大于 1),然后是第二个原子的名字(按字典序),跟着它的数量(如果数量大于 1),以此类推。

示例 1:

输入: 
formula = "H2O"
输出: "H2O"
解释: 
原子的数量是 {'H': 2, 'O': 1}。
示例 2:

输入: 
formula = "Mg(OH)2"
输出: "H2MgO2"
解释: 
原子的数量是 {'H': 2, 'Mg': 1, 'O': 2}。
示例 3:

输入: 
formula = "K4(ON(SO3)2)2"
输出: "K4N2O14S4"
解释: 
原子的数量是 {'K': 4, 'N': 2, 'O': 14, 'S': 4}。
注意:

所有原子的第一个字母为大写,剩余字母都是小写。
formula的长度在[1, 1000]之间。
formula只包含字母、数字和圆括号,并且题目中给定的是合法的化学式。

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

思路

这个问题主要还是去括号,用栈来模拟这个过程,用栈来存储字符串和它出现的次数,遇到右括号的时候,就处理最近左括号到右括号之间的字符
处理完后,栈中存放的就是字符串和次数的键值对
然后,处理栈中的内容,放在哈希表中,然后将哈希表中的内容放在优先队列中根据字典序排序
最后从优先队列中弹出拼接起来,注意1次的就不要拼接数字了
一遍过,超97

class Solution {
    public String countOfAtoms(String formula) {
        //相当于处理括号的题了
        //用栈,遇到左括号入栈,然后将原子和后面的数量入栈,如果后面不是数字,入栈1,碰到右括号,也判断一下后面是否是数字
        int l = formula.length();
        Stack<Node> stack = new Stack<>();
        Map<String, Integer> map = new HashMap<>();
        //遍历字符串
        char[] cc = formula.toCharArray();
        for(int i = 0; i < l; i++){
            char c = cc[i];
            if(c == '('){
                Node node = new Node("(", 0);
                stack.push(node);
            }else if(c == ')'){
                int count = 0;
                //记录后面出现的数字
                while(i + 1 < l && cc[i + 1] >= '0' && cc[i + 1] <= '9'){
                    count = count * 10 + cc[i + 1] - '0';
                    i++;
                }
                //如果后面没有数字,那么就是1次
                if(count == 0)  count = 1;
                //从栈中弹出,处理当前括号,并重新入栈,需要辅助栈
                Stack<Node> temp = new Stack<>();
                while(stack.peek().s != "("){
                    Node node = stack.pop();
                    node.t = node.t * count;
                    temp.push(node);
                }
                //把左括号弹出
                stack.pop();
                //重新入栈
                while(!temp.isEmpty()){
                    stack.push(temp.pop());
                }
            }else{
                //这里处理字母,第一个是大写字母
                StringBuffer sb = new StringBuffer();
                sb.append(c);
                while(i + 1 < l && cc[i + 1] >= 'a' && cc[i + 1] <= 'z'){
                    sb.append(cc[i + 1]);
                    i++;
                }
                int count = 0;
                //记录后面出现的数字
                while(i + 1 < l && cc[i + 1] >= '0' && cc[i + 1] <= '9'){
                    count = count * 10 + cc[i + 1] - '0';
                    i++;
                }
                //如果后面没有数字,那么就是1次
                if(count == 0)  count = 1;
                Node node = new Node(sb.toString(), count);
                stack.push(node);
            }
        }
        //到这里,将所有字符都放在了栈中且处理了括号,然后从栈中取出放入哈希表中
        while(!stack.isEmpty()){
            Node node = stack.pop();
            String s = node.s;
            int t = node.t;
            map.put(s, map.getOrDefault(s, 0) + t);
        }
        //按字符顺序排序
        PriorityQueue<Node> pq = new PriorityQueue<>((a,b) -> a.s.compareTo(b.s));
        for(String s : map.keySet()){
            pq.offer(new Node(s, map.get(s)));
        }
        StringBuffer sb = new StringBuffer();
        while(!pq.isEmpty()){
            Node node = pq.poll();
            sb.append(node.s);
            if(node.t != 1)
                sb.append(node.t);
        }
        return sb.toString();
    }
}

class Node {
    String s;
    int t;
    public Node(String s, int t) {
        this.s = s;
        this.t = t;
    }
}

NC52 括号序列

题目描述
给出一个仅包含字符'(',')','{','}','['和']',的字符串,判断给出的字符串是否是合法的括号序列
括号必须以正确的顺序关闭,"()"和"()[]{}"都是合法的括号序列,但"(]"和"([)]"不合法。
示例1
输入:
"["

返回值:
false

示例2
输入:
"[]"

返回值:
true
思路

用栈来模拟

import java.util.*;


public class Solution {
    /**
     * 
     * @param s string字符串 
     * @return bool布尔型
     */
    public boolean isValid (String s) {
        // write code here
        Stack<Character> stack = new Stack<>();
        int l = s.length();
        for(int i = 0; i < l; i++){
            char c = s.charAt(i);
            if(c == '(' || c == '{' || c == '[')
                stack.push(c);
            if(c == ')'){
                if(stack.isEmpty() || stack.pop() != '(')
                    return false;
            }
            if(c == '}'){
                if(stack.isEmpty() || stack.pop() != '{')
                    return false;
            }
            if(c == ']'){
                if(stack.isEmpty() || stack.pop() != '[')
                    return false;
            }
        }
        return stack.isEmpty();
    }
}

NC102 最近公共祖先

题目描述
给定一棵二叉树(保证非空)以及这棵树上的两个节点对应的val值 o1 和 o2,请找到 o1 和 o2 的最近公共祖先节点。
注:本题保证二叉树中每个节点的val值均不相同。
示例1
输入:
[3,5,1,6,2,0,8,#,#,7,4],5,1
返回值:
3
思路

复习一下,就是递归
递归出口,节点为空,或者节点值是要找的值
否则,向左向右递归,如果左右都能找到值,那么说明最近祖先就是当前节点,返回当前节点的值
如果都找不到,就返回0
如果一个找到了,返回找到的值

import java.util.*;

/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 * }
 */

public class Solution {
    /**
     * 
     * @param root TreeNode类 
     * @param o1 int整型 
     * @param o2 int整型 
     * @return int整型
     */
    public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
        // write code here
        return helper(root, o1, o2);
    }
    
    public int helper(TreeNode node, int x, int y){
        if(node == null)
            return 0;
        if(node.val == x || node.val == y)
            return node.val;
        
        int left = helper(node.left, x, y);
        int right = helper(node.right, x, y);
        //如果左右都为0,那么没有找到,返回0
        if(left == 0 && right == 0)
            return 0;
        else if(left != 0 && right != 0)
            return node.val;
        else
            return left != 0 ? left : right;
    }
}

NC78 反转链表

题目描述
输入一个链表,反转链表后,输出新链表的表头。
示例1
输入:
{1,2,3}
返回值:
{3,2,1}
思路
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head == null)
            return null;
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = head;
        ListNode node = head.next;
        while(node != null){
            ListNode temp = node.next;
            dummy.next = node;
            node.next = pre;
            pre = node;
            node = temp;
        }
        head.next = null;
        return dummy.next;
    }
}

不用虚拟节点

public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head == null)
            return null;

        ListNode pre = null;
        ListNode node = head;
        while(node != null){
            ListNode temp = node.next;
            node.next = pre;
            pre = node;
            node = temp;
        }
        return pre;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值