leetcode 好题目

leetcode25

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

示例:
给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5

说明:
你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

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

思路一:按部就班,顺序每k个元素进行一次翻转,由此,拆解为 单个长度为k的小链表完全翻转,和将整个列表处理成长度为k的小链(就地翻转)
1.长度为k的链表翻转部分
首先要理解链表翻转的本质,即如果t指向当前需要翻转的节点,则需要将t指向t.next的指针指向t的前一个节点(单向链表),为了翻转的连续进行,则需要在翻转前保存pre和next两个指针

链表翻转前

链表翻转

移动pre

移动t

移动next,下一次翻转

翻转

移动pre2

移动t

移动next

翻转完成

这就是一个链表翻转的过程,由于end之后不一定为null,所以控制条件需要注意:

//单个列表反转
    public static ListNode reverseOne(ListNode start,ListNode end){
        if(start==null||end == null)return null;
        ListNode pre=null,t=start,next = t;
        while(t!=null&&pre!=end){
            next = t.next;
            //调整指针
            t.next = pre;
            //更新上面的量
            pre = t;
            t = next;
        }
        return end;
    }

2.整个链表按照长度为k进行翻转
标记start
标记end
翻转链表
处理指针
标记start
标记end
翻转链表
处理指针

//按k个为一组进行反转
    public static ListNode reverseKGroup(ListNode head, int k) {
        ListNode start= head,end=null;
        ListNode v = new ListNode();
        ListNode pre = v,next;
        ListNode h = head;

        for(int c=0;h!=null;h=next,c++){
            next = h.next;
            if(c%k==0)start = h;  
            if(c%k==k-1) {
                //标记end
                end = h;
                reverseOne(start,end);
                //处理指针
                start.next = next;
                pre.next = end;
                pre = start;
            }
        }
        
        return v.next;
    }

题解

class Solution {

    //单个列表反转
    public static ListNode reverseOne(ListNode start,ListNode end){
        if(start==null||end == null)return null;
        ListNode pre=null,t=start,next = t;
        while(t!=null&&pre!=end){
            next = t.next;
            //调整指针
            t.next = pre;
            //更新上面的量
            pre = t;
            t = next;
        }
        return end;
    }

    public ListNode reverseKGroup(ListNode head, int k) {

        ListNode start= head,end=null;
        ListNode v = new ListNode();
        ListNode pre = v,next;
        ListNode h = head;

        for(int c=0;h!=null;h=next,c++){
            next = h.next;
            if(c%k==0)start = h;  
            if(c%k==k-1) {
                //标记end
                end = h;
                reverseOne(start,end);
                //处理指针
                start.next = next;
                pre.next = end;
                pre = start;
            }
        }
        
        return v.next;
    }
}

leetcode 30

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。

注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。

示例 1:

输入:
s = "barfoothefoobarman",
words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。

示例 2:

输入:
s = "wordgoodgoodgoodbestword",
words = ["word","good","best","word"]
输出:[]

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

思路

单词长度都一样,提示采用滑动窗口

需要注意:

  1. 滑动外层是按照字符个数
  2. 滑动内层是按照单词
class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> res = new ArrayList<>();
        
        if(words.length==0)return res;
        int wl = words[0].length();
        if(s.length()<wl)return res;
        //统计单词表中的个数
        Map<String,Integer> wordSet = new HashMap<>();
        for(String word:words){
            wordSet.put(word,wordSet.getOrDefault(word,0)+1);
        }
        
        //注意滑动不是按单词长度而是按字符
        for(int i=0;i<wl;i++){
            int start=i,end=i,cnt =0;
            //统计字符串中出现的个数
            Map<String,Integer> wordMap = new HashMap<>();
            
            //内层按照单词长度开始滑动
            while(end+wl<=s.length()){
                String word = s.substring(end, end+wl);
                end +=wl;
                //不含有该单词
                if(!wordSet.keySet().contains(word)){
                    cnt =0;
                    start = end;
                    wordMap.clear();//中间不能有间隔
                }
                //含有该单词
                else{
                    wordMap.put(word, wordMap.getOrDefault(word,0)+1);
                    cnt++;
                    while(wordMap.get(word)>wordSet.get(word)){
                        String popWord = s.substring(start,start+wl);
                        start += wl;
                        cnt--;
                        wordMap.put(popWord, wordMap.get(popWord)-1);
                    }
                    if(cnt==words.length)res.add(start);
                }
            }

        }
        return res;
    }
}

leetcode 37

编写一个程序,通过填充空格来解决数独问题。

一个数独的解法需遵循如下规则:

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

空白格用 '.' 表示。

一个数独。

答案被标成红色。

提示:

给定的数独序列只包含数字 1-9 和字符 '.' 。
你可以假设给定的数独只有唯一解。
给定数独永远是 9x9 形式的。

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

思路一: 回溯法

lass Solution {
    boolean[][] cols = new boolean[9][9];
    boolean[][] rows = new boolean[9][9];
    boolean[][][] blocks = new boolean[3][3][9];
    List<Integer[]> spaces = new ArrayList<>();
    boolean valid = false;

    public void solveSudoku(char[][] board) {
        
        for(int y =0;y<9;y++){
            for(int x=0;x<9;x++){
                if(board[y][x]=='.'){
                    spaces.add(new Integer[]{y,x});
                }
                else{
                    int digit = board[y][x]-'1';
                    rows[y][digit]=true;
                    cols[x][digit]=true;
                    blocks[y/3][x/3][digit]=true;
                }
            }
        }
        dfs(board,0);
    }

    public void dfs(char[][] board,int step){
        if(step==spaces.size()){
            valid = true;
            return;
        }   
        Integer[] space = spaces.get(step);
        Integer i = space[0],j = space[1];
        for(int digit = 0;digit<9&&!valid;digit++){
            if(!cols[j][digit]&&!rows[i][digit]&&!blocks[i/3][j/3][digit]){
                cols[j][digit]=rows[i][digit]=blocks[i/3][j/3][digit]=true;
                board[i][j]= (char)(digit+'1');
                dfs(board, step+1);
                cols[j][digit]=rows[i][digit]=blocks[i/3][j/3][digit]=false;
            }
        }
    }
}

leetcode41

  1. 缺失的第一个正数
    给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。

进阶:你可以实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案吗?

示例 1:

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

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

输入:nums = [7,8,9,11,12]
输出:1

提示:

0 <= nums.length <= 300
-231 <= nums[i] <= 231 - 1

package leetcode;

import utils.ArrayUtils;

public class l41 {
    public static int firstMissingPositive(int[] nums) {
        int N = nums.length + 1;
        for (int i = 1; i <= nums.length; i++) {
            if (nums[i - 1] <= 0) nums[i - 1] = N;
        }
        ArrayUtils.printArray(nums);
        for (int i = 1; i <= nums.length; i++) {
            int idx = Math.abs(nums[i - 1]);
            if (idx > 0 && idx < N) {
                nums[idx - 1] = -Math.abs(nums[idx - 1]);
            }
        }
        ArrayUtils.printArray(nums);
        for (int i = 1; i <= nums.length; i++) {
            if (nums[i - 1] > 0) return i;
        }
        return nums.length + 1;
    }

    public static void main(String[] args) {
        int[] nums = {3, 4, -1, 1};
//        int[] nums = {1, 2, 0};
        ArrayUtils.printArray(nums);
        int res = firstMissingPositive(nums);
        System.out.println(res);
    }
}

leetcode42 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

提示:

n == height.length
0 <= n <= 3 * 104
0 <= height[i] <= 105

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


leetcode44

给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?' 和 '*' 的通配符匹配。

'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。

说明:

s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。

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

package leetcode;

import javafx.scene.effect.DisplacementMap;

public class l44 {
    public static boolean isMatch(String s, String p) {
        //s = "adceb"
        //p = "*a*b"
        char[] sChars = s.toCharArray();
        char[] pChars = p.toCharArray();
        boolean[][] m = new boolean[sChars.length + 1][pChars.length + 1];
        m[0][0] = true;
        for (int i = 1; i <= p.length() && pChars[i - 1] == '*'; i++) {
            m[0][i] = true;
        }
        for (int i = 1; i <= sChars.length; i++) {
            for (int j = 1; j <= pChars.length; j++) {
                if (pChars[j - 1] == '*') {
                    m[i][j] |= m[i][j - 1];
                    m[i][j] |= m[i - 1][j];
                } else if (pChars[j - 1] == '?' || sChars[i - 1] == pChars[j - 1]) {
                    m[i][j] |= m[i - 1][j - 1];
                }
            }
        }
        return m[sChars.length][pChars.length];
    }


    public static void main(String[] args) {
        String s = "adceb";
//        String p = "*a*b";
        String p = "a*c?b";
        System.out.println(isMatch(s, p));
    }
}

leetcode 647 回文子串统计 manacher算法

//方法一:搬砖法
    public static int countSubstrings1(String s) {
        int count = 0;
        for (int i = 0; i < s.length(); i++) {
            for (int j = i + 1; j <= s.length(); j++) {
                if (isPalindrome(s.substring(i, j))) count++;
            }
        }
        return count;
    }

    public static boolean isPalindrome(String s) {
        char[] chars = s.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            if (chars[i] != chars[chars.length - 1 - i]) {
                return false;
            }
        }
        return true;
    }

    //方法二:中心扩展法
    public static int countSubstrings2(String s) {
        int count = 0;
        int n = s.length();
        for (int i = 0; i < 2 * n - 1; i++) {
            int l = i / 2, r = i / 2 + i % 2;
            while (l >= 0 && r < n && s.charAt(l) == s.charAt(r)) {
                l--;
                r++;
                count++;
            }
        }
        return count;
    }
    
    //方法三:Manacher 算法
    /*
    f[i]指以i为中心的最大回文半径
    iMax指[1,i-1]的最大回文中心
    rMax指[i,i-1]的最大回文右边界
    这种方法的思想说白了还是中心扩展法,只是需要维护一个iMax,和rMax来利用之前的数据对f[i]进行初始化以最大化利用之前的结果
    每个位置能够参与组成回文串的个数正好是以该位置为中心的最大回文半径,又 f[i]是做了manacher处理后的字符串,对于原始长度为L的字符串处理后有 2*f[i]-1 = 2*L+1,得 L= f[i]-1
    */
    public static int manacher(String s) {
        //字符串初始化
        char[] chars = s.toCharArray();
        int size = 2 * s.length() + 3;
        char[] t = new char[size];
        t[0] = '$';
        t[1] = '#';
        for (int i = 0, j = 2; i < chars.length; i++) {
            t[j++] = chars[i];
            t[j++] = '#';
        }
        t[size - 1] = '!';
        //开始马拉车
        int[] f = new int[size];//f[i]表示以i为中心的最大回文
        int iMax = 0, rMax = 0, ans = 0;
        //计算长度时需要去掉index为0的$和最后的size-1的!
        for (int i = 1; i < size - 1; i++) {
            //初始化
            f[i] = (i <= rMax) ? Math.min(rMax - i + 1, f[2 * iMax - i]) : 1;
            //中心扩展
            while (t[i - f[i]] == t[i + f[i]]) ++f[i];
            //动态维护iMax,rMax
            if (i + f[i] - 1 > rMax) {
                iMax = i;
                rMax = i + f[i] - 1;
            }
            // 统计答案, 当前贡献为 (f[i] - 1) / 2 上取整
            ans += f[i] / 2;
        }
        return ans;
    }

posted @ 2021-03-04 09:51  Fake_coder  阅读(88)  评论(0编辑  收藏  举报