leetcode例题

15.三数之和(threeSum)

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

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

示例 2:
输入:nums = []
输出:[]

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

思路

排序 + 双指针

因为在循环枚举三个元素时会遇到重复的结果,枚举结束还要进行去重,时间太慢,所以先用Arrays.sort(nums);排序,同时,对于每一重循环而言,相邻两次枚举的元素不能相同,否则也会造成重复。举个例子,如果排完序的数组为[0, 1, 2, 2, 2, 3],我们使用三重循环枚举到的第一个三元组为 (0, 1, 2)(0,1,2),如果第三重循环继续枚举下一个元素,那么仍然是三元组 (0, 1, 2)(0,1,2),产生了重复。因此我们需要将第三重循环「跳到」下一个不相同的元素,即数组中的最后一个元素 33,枚举三元组 (0, 1, 3)(0,1,3)。
使用双指针法在第二层嵌套中将第三个元素的指针不停左移,因为当前两个元素确定时,第三个元素也是唯一的,所以当第三个指针指向正确的元素时第二层循环也可j++遍历下一个元素。因为数组进行过排序的操作,所以第三个元素的指针只需一直左移,因为第二个元素的指针右移的同时第二个元素也在增大,所以比第三个指针的元素大的元素不用考虑。
代码如下:

public class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int len = nums.length;
        List<List<Integer>> re = new ArrayList<>();
        Arrays.sort(nums);
        for(int i = 0; i <= len - 3; i++){
            if(i > 0 && nums[i] == nums[i - 1]){
                continue;
            }
            int n_point = len - 1;
            for(int j = i + 1; j <= len - 2; j++){
                if(j > i + 1 && nums[j] == nums[j-1]){
                    continue;
                }
                while(n_point > j && (nums[n_point] + nums[j] + nums[i] > 0)) {
                    n_point --;
                }
                if (n_point == j){
                    break;
                }
                if (nums[j] + nums[n_point] + nums[i] == 0) {
                    List<Integer> l = new ArrayList<Integer>();
                    l.add(nums[i]);
                    l.add(nums[j]);
                    l.add(nums[n_point]);
                    re.add(l);
                }
            }
        }
        return re;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        int[] a = {1,2,-2,-1};
        System.out.println(s.threeSum(a));
    }
}

22.括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入:n = 1
输出:["()"]

方法:回溯递归

回溯的灵魂就是画出树结构图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uzAfYIiN-1629115460314)(https://note.youdao.com/yws/res/3823/WEBRESOURCE21c6b317ca983f4cbaf1ae7d984d2218)]

如图,先DFS遍历所有节点,得出结果如下:
['((((', '((()', '(()(', '(())', '()((', '()()', '())(', '()))', ')(((', ')(()', ')()(', ')())', '))((', '))()', ')))(', '))))']

接着再过滤掉无效解即可,即左边括号+1,右边括号-1,判断最后值是否为0,以及中途值是否会小于0即可。

public class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> l = new ArrayList<>();
        if (n == 0){
            return l;
        }
        generate("",l,n);
//        System.out.println(l);
        return l;
    }
    public void generate(String s, List<String> l,int n){
        int len = s.length();
        if (len == n * 2){
            int count = 0;
            /** 检测是否为有效解 **/
            for(int i = 0; i < len;i ++){
                if (s.charAt(i) == '('){
                    count ++;
                }else {
                    count --;
                }
                if (count < 0) {
                    return;
                }
            }
            if(count == 0){
                l.add(s);
                return;
            }else {
                return;
            }
        }
        generate(s+"(",l,n);
        generate(s+")",l,n);
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        s.generateParenthesis(2);
    }
}

17.电话号码的字母组合

和上面类似,同样使用回溯法,但这里并非二叉树,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z0eczD1k-1629115460316)(https://note.youdao.com/yws/res/3821/WEBRESOURCEf0ab995f2d930462f18d54bb786719d1)]
对于这种多叉树,遍历如下代码所示:

class Solution {
    public List<String> letterCombinations(String digits) {
        List<String> combinations = new ArrayList<String>();
        if (digits.length() == 0) {
            return combinations;
        }
        Map<Character, String> phoneMap = new HashMap<Character, String>() {{
            put('2', "abc");
            put('3', "def");
            put('4', "ghi");
            put('5', "jkl");
            put('6', "mno");
            put('7', "pqrs");
            put('8', "tuv");
            put('9', "wxyz");
        }};
        backtrack(combinations, phoneMap, digits, 0, new StringBuffer());
        return combinations;
    }

    public void backtrack(List<String> combinations, Map<Character, String> phoneMap, String digits, int index, StringBuffer combination) {
        if (index == digits.length()) {
            combinations.add(combination.toString());
        } else {
            char digit = digits.charAt(index);
            String letters = phoneMap.get(digit);
            int lettersCount = letters.length();
            for (int i = 0; i < lettersCount; i++) {
                combination.append(letters.charAt(i));
                backtrack(combinations, phoneMap, digits, index + 1, combination);
                combination.deleteCharAt(index);
            }
        }
    }
}

39.组合总和

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。

candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。

对于给定的输入,保证和为 target 的唯一组合数少于 150 个。

示例 1:

输入: candidates = [2,3,6,7], target = 7
输出: [[7],[2,2,3]]

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

list删除某些元素时,当删除当前节点时只需将下标–即可,因为边界条件是j<re.length(),所以最后一次循环不会越界。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NjyLlJiR-1629115460317)(https://note.youdao.com/yws/res/3822/WEBRESOURCEdcabdccf3fc0e9cfe0717507d724db7b)]

其次,如果需要将list b添加到list a中,此时如果list b的值改变,则list a中对应的值也会改变,需要使用以下方法进行深复制:
re.add(new ArrayList<Integer>(tmp))

上式即将tmp的值复制给re

这一题的一个题解中给出了剪枝的方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dDgZNRy5-1629115460319)(https://note.youdao.com/yws/res/d/WEBRESOURCE7cc441060d9408d4932aaecbf3b8e75d)]

如上图所示,即将一个数字和其他数字的所有组合之后,同一层的其他递归不再考虑当前数字。代码如下

private void dfs(int[] candidates, int begin, int len, int target, Deque<Integer> path, List<List<Integer>> res) {
        // target 为负数和 0 的时候不再产生新的孩子结点
        if (target < 0) {
            return;
        }
        if (target == 0) {
            res.add(new ArrayList<>(path));
            return;
        }

        // 重点理解这里从 begin 开始搜索的语意
        for (int i = begin; i < len; i++) {
            path.addLast(candidates[i]);

            // 注意:由于每一个元素可以重复使用,下一轮搜索的起点依然是 i,这里非常容易弄错
            dfs(candidates, i, len, target - candidates[i], path, res);

            // 状态重置
            path.removeLast();
        }
    }

46.全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

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

示例 2:

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

本题看理解对递归与回溯的理解:

我的做法如下,每层新递归都使用新对象来存储当前对数据操作后的结果,下一层递归对当前层递归没有产生影响。

public void findre(List<Integer> nums,List<Integer> tmp,int l){
        if(tmp.size() == l){
            re.add(tmp);
            return;
        }else {
            for(int j = 0;j < nums.size();j++){
                tmp.add(nums.get(j));
                // 使用新对象num_tmp将当前遍历过的数字删去
                List<Integer> num_tmp = new ArrayList<>(nums);
                num_tmp.remove(nums.get(j));
                // 使用新对象tmp存储当前递归到的结果
                findre(num_tmp,new ArrayList<Integer>(tmp),l);
                tmp.remove(tmp.size() - 1);
            }
        }
    }

解答中使用回溯是这样做的:

private void dfs(int[] nums, int len, int depth,
                     Deque<Integer> path, boolean[] used,
                     List<List<Integer>> res) {
        if (depth == len) {
            res.add(new ArrayList<>(path));
            return;
        }

        for (int i = 0; i < len; i++) {
            //使用used数组来记录当前元素有没有被用过,因为每一次递归调用都是线性的,所以在递归函数调用后再
            将是否用过的used标志置为false,同时在下一层递归使用过path记录递归结果之后再将path当前元素删除,
            一个path对象就可以继续用。
            if (!used[i]) {
                path.addLast(nums[i]);
                used[i] = true;

                System.out.println("  递归之前 => " + path);
                dfs(nums, len, depth + 1, path, used, res);

                used[i] = false;
                path.removeLast();
                System.out.println("递归之后 => " + path);
            }
        }

回溯可以更加节省空间,让下一层递归之后的结果回溯回来影响当前层的结果。

49.字母异位词分组

给定一个字符串数组,将字母异位词组合在一起。可以按任意顺序返回结果列表。

字母异位词指字母相同,但排列不同的字符串。

示例 1:

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

示例 2:

输入: strs = [""]
输出: [[""]]

思路:使用一层循环,遍历输入的数组,对数组中的String进行排序,排序后作为map的key,对相同key的map插入数据即可。如下:

public List<List<String>> groupAnagrams1(String[] strs) {
        List<List<String>> res = new ArrayList<>();
        List<String> res_tmp = new ArrayList<>();
        Map<String , List<String>> map = new HashMap<String, List<String>>();
        for(int i = 0; i < strs.length;i ++){
            char[] s = strs[i].toCharArray();
            Arrays.sort(s);
            String k = new String(s);
            //如果map中有k,则返回map.get(k),否则返回new ArrayList<String>()
            List<String> l = map.getOrDefault(k, new ArrayList<String>());
            l.add(strs[i]);
            map.put(k,l);
        }
        return new ArrayList<List<String>>(map.values());
    }

其中:字符串转字符数组:char[] s = strs[i].toCharArray();

数组排序:Array.sort(char);

map转list:new ArrayList<List<String>>(map.values())

56.合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

此题可以先通过数组第一位排序,排序之后进行区间合并,如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZQ5MAe9f-1629115460320)(https://note.youdao.com/yws/res/e/WEBRESOURCE74b906dc67a77393bccbdd61948b87fe)]

排序使用快速排序,快排代码如下:

public void quicksort(int[] a,int low,int high){
        if(low < high) {
            int i = low;
            int j = high;
            int tmp = a[low];
            while (low < high) {
                //因为基准tmp一开始存的是a[low],所以就要从high的那一边找到一个比基准小的进行交换。
                while (high > low && a[high] >= tmp) {
                    high--;
                }
                a[low] = a[high];
                while (low < high && a[low] <= tmp) {
                    low++;
                }
                a[high] = a[low];
            }
            a[low] = tmp;
            quicksort(a, i, low - 1);
            quicksort(a, low + 1, j);
        }
    }

先初始化俩指针low 、high分别指向第一个和最后一个元素,之后将基准赋值为a[low]的值,接着从high开始遍历,如果没有比基准小的元素则–,如果有,则将a[low]=a[high],接着从low开始遍历,如果没有没有比基准大的元素则++,如果有,则将a[high]=a[low],直到low>=high。通过第一次快排,此时基准左边的值全部小于基准,基准右边全部大于,此时再递归调用自身对两边进行快排即可。递归停止的条件是low >= high。

解答中直接调用了java内置方法,如下

Arrays.sort(intervals, new Comparator<int[]>() {
            public int compare(int[] interval1, int[] interval2) {
                return interval1[0] - interval2[0];
            }
        });

待弄懂。

78.子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

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

示例 2:

输入:nums = [0]
输出:[[],[0]]

本题用到了39题的思路剪枝,即将当前数字与其他所有数字的组合考虑过之后,同一层的递归不再考虑当前元素。

public void findchild(int high,int begin,int n,List<Integer> l,int[] nums){
        //同一层递归开始的数字逐渐增大
        for (int i = begin; i < nums.length; i++){
            l.add(nums[i]);
            if(n == high){
                res.add(new ArrayList<Integer>(l));
            }
            //下一层递归从i+1开始
            findchild(high, i + 1, n + 1, l, nums);
            //本次递归用完之后删除列表最后一个元素,从而始终用一个列表完成存储,回溯的思想
            l.remove(l.size()-1);
        }
    }

96. 不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树有多少种?返回满足题意的二叉搜索树的种数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0j3RvsZJ-1629115460321)(https://note.youdao.com/yws/res/e/WEBRESOURCE95f8b1efc4881833e4f069ccbe65226e)]

思路

给定一个有序序列 1⋯n,为了构建出一棵二叉搜索树,我们可以遍历每个数字 i,将该数字作为树根,将 1⋯(i−1) 序列作为左子树,将 (i+1)⋯n 序列作为右子树。接着我们可以按照同样的方式递归构建左子树和右子树。

给定序列 1⋯n,我们选择数字 i 作为根,则根为i的所有二叉搜索树的集合是左子树集合和右子树集合的笛卡尔积,对于笛卡尔积中的每个元素,加上根节点之后形成完整的二叉搜索树,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AowEW7ja-1629115460322)(https://note.youdao.com/yws/res/4/WEBRESOURCE3ce127f6acfdf6079aa669d805318d64)]

代码如下:

public int numTrees(int n) {
        int[] t = new int[n + 1];
        t[0] = 1;
        t[1] = 1;
        for(int i = 2; i <= n; i++){
            for(int j = 1; j <= i; j ++){
                t[i] = t[j - 1] * t[i - j];
            }
        }
        return t[n];
    }

总结:本题使用动态规划的方法,对于1···n这样一串数字作为二叉搜索树,等于每个数字作为根节点的二叉树的数量总和,而当前数字作为根节点的二叉树数量又等于左子树和右子树数量相乘。

101. 对称二叉树

给定一个二叉树,检查它是否是镜像对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

    1
   / \
  2   2
 / \ / \
3  4 4  3

但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

    1
   / \
  2   2
   \   \
   3    3

思路:使用两个指针同时往下遍历,看指针是否相等即可,如下:

public boolean isSymmetric(TreeNode root) {
        return findres(root.left, root.right);
    }

    public boolean findres(TreeNode lnode, TreeNode rnode){
        if(lnode == null && rnode == null){
            return true;
        }
        if(lnode != null && rnode != null && lnode.val == rnode.val){
            return findres(lnode.left, rnode.right) && findres(lnode.right, rnode.left);
        }else {
            return false;
        }
    }

105. 从前序与中序遍历序列构造二叉树

给定一棵树的前序遍历 preorder 与中序遍历 inorder。请构造二叉树并返回其根节点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TAwLztKo-1629115460323)(https://note.youdao.com/yws/res/a/WEBRESOURCE1c944ca8d9fa5c161c6ecc144545f14a)]

前序遍历数组preorder中第一个便是根节点,在inorder中找到这个元素,于是此元素左边便是左子树,右边是右子树,而在preorder中除去前(左子树元素个数+当前根节点个数1)便也是右子树元素个数。代码如下:

public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder.length > 0) {
            TreeNode root = new TreeNode(preorder[0]);
            for (int i = 0; i < inorder.length; i++) {
                if (inorder[i] == preorder[0]) {
                    if (i > 0)
                        root.left = buildTree(Arrays.copyOfRange(preorder, 1, i + 1), Arrays.copyOfRange(inorder, 0, i));
                    if (i + 1 < preorder.length)
                        root.right = buildTree(Arrays.copyOfRange(preorder, i + 1, preorder.length), Arrays.copyOfRange(inorder, i + 1, preorder.length));
                    break;
                }
            }
            return root;
        }
        return null;
    }

注:数组分割函数Arrays.copyOfRange(inorder, 0, i)是分割[0,i)的长度,注意括号。

128. 最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

示例 2:

输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9

本题需要用到哈西表先去重,之后在一次for循环中如果找到了连续数字的开始数字,那么就循环查询哈西表,每次给数字加一查询该数字是否在表中,此处默认查询哈西表的时间消耗可忽略,所以本题只用遍历两次数组,一次用来存储,一次用来查询,时间复杂度为O(n)。代码如下:

public int longestConsecutive1(int[] nums) {
        if(nums.length == 0)
            return 0;
        HashSet<Integer> s = new HashSet<>();
        for(int num : nums){
            s.add(num);
        }
        int len = 1;
        int num_tmp;
        int max = 1;
        for(int tmp : s){
            if(!s.contains(tmp - 1)){
                len = 1;
                num_tmp = tmp;
            }else {
                continue;
            }
            while(s.contains(num_tmp + 1)){
                len ++;
                num_tmp ++;
            }
            max = Math.max(max,len);
        }
        return max;
    }

139. 单词拆分

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:

拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

示例 1:

输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。

示例 2:

输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
     注意你可以重复使用字典中的单词。

使用动态规划,当前节点的状态值表示前n-1位是否为true。

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        boolean[] a = new boolean[s.length() + 1];
        a[0] = true;
        for(int i = 1; i <= s.length(); i++){
            for(int j = 0; j < i; j ++){
            //如果a[j]为true并且字符串中j到i位包含在list中则a[i]为true。
                if(a[j] && wordDict.contains(s.substring(j, i))){
                    a[i] = true;
                    break;
                }
            }
        }
        return a[s.length()];
    }
}

141.环形链表

给定一个链表,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

如果链表中存在环,则返回 true 。 否则,返回 false 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MHcpIYuy-1629115460324)(https://note.youdao.com/yws/res/b/WEBRESOURCE612f20fd8d492d6329815ade5133616b)]

两种方法,一种用哈西表查询,如下:

public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> seen = new HashSet<ListNode>();
        while (head != null) {
            if (!seen.add(head)) {
                return true;
            }
            head = head.next;
        }
        return false;
    }
}

方法二:快慢指针

在这里插入图片描述
在这里插入图片描述

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) {
            return false;
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while (slow != fast) {
            if (fast == null || fast.next == null) {
                return false;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        return true;
    }
}

148. 排序链表

由本题得到的关于list排序的技巧:
以下三种方法等价:

l.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });
        
        
l.sort((Integer i, Integer j) ->{return i - j;});


Collections.sort(l);

若l中存储的元素不是基本类型,而是list、数组这样的对象,则要用前两种方法,将具体比较的元素写出来。

(双冒号怎么写带弄懂)

同时,hashset取元素需要用到迭代器:

Iterator<String> it = set.iterator();
if(it.hasNext()) it.next();

175. 组合两个表

表1: Person

+-------------+---------+
| 列名         | 类型     |
+-------------+---------+
| PersonId    | int     |
| FirstName   | varchar |
| LastName    | varchar |
+-------------+---------+
PersonId 是上表主键

表2: Address

+-------------+---------+
| 列名         | 类型    |
+-------------+---------+
| AddressId   | int     |
| PersonId    | int     |
| City        | varchar |
| State       | varchar |
+-------------+---------+
AddressId 是上表主键

编写一个 SQL 查询,满足条件:无论 person 是否有地址信息,都需要基于上述两表提供 person 的以下信息:

FirstName, LastName, City, State

Select p.FirstName ,p.LastName, a.City, a.State  from Person p left join Address a on p.PersonId = a.PersonId;

Select p.FirstName ,p.LastName, a.City, a.State  from Person p , Address a where p.PersonId = a.PersonId;

本题用左连接而不直接连接是因为左连接把左边的表中右边表不包含的部分也输出出来,如果直接连接,当两个表无交集的时候输出为空,达到不了“无论 person 是否有地址信息,都需要基于上述两表提供 person 的以下信息”的目标。

178. 分数排名

编写一个 SQL 查询来实现分数排名。

如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。

+----+-------+
| Id | Score |
+----+-------+
| 1  | 3.50  |
| 2  | 3.65  |
| 3  | 4.00  |
| 4  | 3.85  |
| 5  | 4.00  |
| 6  | 3.65  |
+----+-------+

例如,根据上述给定的 Scores 表,你的查询应该返回(按分数从高到低排列):

+-------+------+
| Score | Rank |
+-------+------+
| 4.00  | 1    |
| 4.00  | 1    |
| 3.85  | 2    |
| 3.65  | 3    |
| 3.65  | 3    |
| 3.50  | 4    |
+-------+------+

本题代码如下:
解法一:

select 
b.score,a.rank 
from 
  (select  score ,convert((@rank := @rank + 1), signed) 'rank' 
  from 
    (select @rank := 0)  ranktable,(select distinct score from Scores order by score desc) s) a, scores b 
where 
a.score=b.score order by a.rank;

其中@符号:可以自定义一个临时变量

首先使用(select @rank := 0)初始化临时变量表,将这个表与去重之后的分数表连接,再通过外层select中@rank := @rank + 1每次rank+1得到分数排名。因为此时的分数是去过重的,所以在最外层再将未去重的score表和查询到的rank表连接即可。

注:单独命名别名为rank,'rank’需要用引号扩起来,因为rank是mysql中的关键字,否则会报错。

注:convert((@rank := @rank + 1), signed)表示将得到的排名为1.0、2.0这种小数转变为整数

解法二:

开窗函数 (MySQL开窗函数

select score, dense_rank() over(order by score desc) 'rank' from scores

518. 零钱兑换 II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例 1:

输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。

示例 3:

输入:amount = 10, coins = [10] 
输出:1

思路:拿第一个例子来说,amount = 5, coins = [1, 2, 5]先计算由硬币1组成的情况,之后再加上由2组成的所有情况,最后再加上由5组成的所有情况,这样按顺序计算避免了重复。

class Solution {
    public int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        
        dp[0] = 1;
        for (int i = 0; i < coins.length; i++) {
            for (int j = coins[i]; j <= amount; j++) {
                dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值