【2021-03】Leetcode 前200 中等题 Java一刷

说明:按照题目出现频率刷。

92 反转链表 II

题目描述:反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
题目地址:https://leetcode-cn.com/problems/reverse-linked-list-ii/
我的想法:扫描一次链表,找出left对应的结点以及left前面的结点,找出right对应的结点以及right后面的结点,先翻转left到right区间的链表然后修改边界指针的指向即可。
需要注意边界条件:left是否是第一个结点以及right是否是最后一个结点,后续拼接时需要注意处理上述情况。
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:35.8 MB, 在所有 Java 提交中击败了93.58%的用户

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        if(head==null || left==right) return head;
        ListNode lptr = head;//标记left对应的结点
        ListNode lbef = head;//标记left前面的结点
        if(left!=1)
            for(int i=1;i<left;i++){
                lbef = lptr;
                lptr = lptr.next;
            }
        ListNode rptr = lptr;//标记right对应的结点
        ListNode raft = rptr;//标记right后面的结点
        for(int i=left;i<right;i++){
            rptr = rptr.next;
            raft = rptr.next;
        }
        ListNode p = lptr;
        ListNode s = p.next;
        while(s!=raft){//改变left~right的指针指向
            ListNode tmp = s.next;
            s.next = p;
            p = s; s = tmp;
        }
        //处理衔接部分
        if(lptr==lbef){
            lptr.next = raft;
            head = rptr;
        }else{
            lptr.next = raft;
            lbef.next = rptr;
        }
        return head;
    }
}

200 岛屿数量

题目描述:给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
题目地址:https://leetcode-cn.com/problems/number-of-islands/
我的想法:用dfs可以解决,每次将岛屿改成0不需要再变回去。
执行用时:2 ms, 在所有 Java 提交中击败了92.58%的用户
内存消耗:41 MB, 在所有 Java 提交中击败了31.40%的用户

class Solution {
    private int rows;//行数
    private int cols;//列数
    private int cnt = 0;
    private int[] dx = {-1,1,0,0};
    private int[] dy = {0,0,-1,1};
    public void dfs(char[][] grid,int x,int y){//把连接的1都变成0
        if(x<0 || y<0 || x>=rows || y>=cols) return ;//越界
        if(grid[x][y]=='1'){
            grid[x][y] = '0';
            for(int i=0;i<4;i++)
                dfs(grid,x+dx[i],y+dy[i]);
        }
    }
    public int numIslands(char[][] grid) {
        rows = grid.length;
        if(rows==0) return 0;
        cols = grid[0].length;
        if(cols==0) return 0;
        for(int i=0;i<rows;i++)
            for(int j=0;j<cols;j++)
                if(grid[i][j]=='1'){
                    cnt++;
                    dfs(grid,i,j);//如果是1就进入dfs搜索
                }
        return cnt;
    }
}

103 二叉树的锯齿形层序遍历

题目描述:给定一个二叉树,返回其节点值的锯齿形层序遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
题目地址:https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/
我的想法:用一个队列存储每层的结点,每层存储该层的下一层结点时需要交替变化方向。
执行用时:1 ms, 在所有 Java 提交中击败了98.87%的用户
内存消耗:38.4 MB, 在所有 Java 提交中击败了87.25%的用户

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> list = new ArrayList<>();
        if(root==null) return list;
        List<TreeNode> q = new LinkedList<>();
        q.add(root);//入队
        boolean dct = true;
        while(!q.isEmpty()){
            int sz = q.size();
            List<Integer> l = new ArrayList<>();
            for(int i=0;i<sz;i++){
                TreeNode tmp = q.get(sz-i-1);
                l.add(tmp.val);
                q.remove(sz-i-1);
                if(dct){
                    if(tmp.left!=null) q.add(tmp.left);
                    if(tmp.right!=null) q.add(tmp.right);
                }else{
                    if(tmp.right!=null) q.add(tmp.right);
                    if(tmp.left!=null) q.add(tmp.left);
                }
            }
            dct = !dct;
            list.add(new ArrayList(l));
        }
        return list;
    }
}

146 LRU 缓存机制

题目描述:运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:
LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?
题目地址:https://leetcode-cn.com/problems/lru-cache/
我的想法:看到O(1)立马想到HashMap,还要考虑cache的生命因为要满足O(1)所以不能采用用数字表示使用时间的做法,这样修改数字需要O(n),为了插入删除方便用双向链表
执行用时:19 ms, 在所有 Java 提交中击败了71.76%的用户
内存消耗:46.6 MB, 在所有 Java 提交中击败了34.25%的用户

class LRUCache {
    private int capacity;
    private int size;
    class Node{
        private int key,value;
        private Node left,right;
        Node(){}
        public Node(int k,int v){this.key = k;this.value = v;}
    }
    private Map<Integer,Node> cache;
    private Node head;
    private Node tail;
    public LRUCache(int capacity) {
        this.capacity = capacity;
        size = 0;
        head = new Node();
        tail = new Node();
        head.right = tail;
        tail.left = head;
        cache = new HashMap<>();
    }

    //将已存在结点移至后面
    public void moveToTail(Node node){
        node.left.right = node.right;
        node.right.left = node.left;
        insertToTail(node);
    }

    //将新结点移至后面
    public void insertToTail(Node node){
        node.right = tail;
        node.left = tail.left;
        tail.left.right = node;
        tail.left = node;
    }
    
    //删除最旧的结点
    public void deleteHead(){
        Node node = head.right;
        node.right.left = head;
        head.right = node.right;
        cache.remove(node.key);
        node = null;
    }

    public int get(int key) {
        Node node = cache.get(key);
        if(node==null) return -1;
        moveToTail(node);
        return node.value;
    }
    
    public void put(int key, int value) {
        Node node = cache.get(key);
        if(node==null){
            node = new Node(key,value);
            insertToTail(node);
            cache.put(key,node);
            ++size;
            if(size>capacity){
                deleteHead();
            }
        }else{
            node.value = value;
            moveToTail(node);
        }
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

143 重排链表

题目描述:给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
题目地址:https://leetcode-cn.com/problems/reorder-list/

第一种解法(要快要方便就哈希):用HashMap标记各个结点的位置比较简单些。
执行用时:11 ms, 在所有 Java 提交中击败了7.59%的用户
内存消耗:41.1 MB, 在所有 Java 提交中击败了46.53%的用户

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public void reorderList(ListNode head) {
        if(head==null || head.next==null) return ;
        Map<Integer,ListNode> map = new HashMap<>();
        int cnt = 0;
        ListNode node = head;
        while(node!=null){
            map.put(cnt++,node);
            node = node.next;
        }
        int mid = (cnt+1)/2;
        ListNode left = null,right = null;
        for(int i=0;i<mid;i++){
            left = map.get(i);
            right = map.get(cnt-i-1);
            right.next = left.next;
            left.next = right;
        }
        right.next = null;
    }

}

第二种解法(反转链表):用快慢指针分别标记链表的中间部分和结尾部分,之后再翻转链表的后半部分,再迭代后半部分插入前半部分。注意循环链是否会出现。
执行用时:2 ms, 在所有 Java 提交中击败了80.37%的用户
内存消耗:41.1 MB, 在所有 Java 提交中击败了53.76%的用户

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public void reorderList(ListNode head) {
        if(head==null || head.next==null) return ;
        ListNode slow = head,fast = head;//快慢指针,用于标记中间结点和结尾结点
        boolean go = false;
        while(fast.next!=null){
            fast = fast.next;
            if(go) slow = slow.next;
            go = !go;
        }
        //将中间结点之后的链表反转
        ListNode p = slow;
        ListNode s = slow.next;
        while(s!=null){
           ListNode t = s.next;
           s.next = p;
           p = s;s = t; 
        }
        ListNode node = head;
        //将结点依次插入对应位置
        while(node!=slow.next){
            ListNode tmp = fast.next;
            fast.next = node.next;
            node.next = fast;
            node = fast.next;
            fast = tmp;
        }
        slow.next.next = null;
    }
}

5 最长回文子串

题目描述:给你一个字符串 s,找到 s 中最长的回文子串。
题目地址:https://leetcode-cn.com/problems/longest-palindromic-substring/

第一种解法(动态规划):一般这种题都要想到动态规划,dp[i][j]表示s[i]到s[j]闭区间形成的字符子串是否是回文串,然后想好状态怎么转移。
执行用时:435 ms, 在所有 Java 提交中击败了11.37%的用户
内存消耗:42.7 MB, 在所有 Java 提交中击败了35.10%的用户

class Solution {
    public String longestPalindrome(String s) {
        if(s.equals("")) return s;
        boolean[][] dp = new boolean[s.length()][s.length()];
        for(int i=0;i<s.length();i++)
            dp[i][i] = true;
        int cnt = 0;//默认回文串长度
        int left = 0,right = 0;//回文串边界
        for(int i=s.length()-1;i>=0;i--){
            for(int j=0;j<s.length();j++){
                if(i==j || i>j) continue;
                if((i+1)==j) dp[i][j] = (s.charAt(i)==s.charAt(j));
                else dp[i][j] = (s.charAt(i)==s.charAt(j) && dp[i+1][j-1]);
                if(dp[i][j] && (j-i+1)>=cnt){
                    cnt = j-i+1;
                    left = i;right = j;
                }
            }
        }
        return s.substring(left,right+1);
    }
}

第二种解法(中心扩散):从题解看到的解题思想,每次以一个结点或者两个紧挨的结点为中心向左右扩散,如果两端出现不一样的字符就break退出循环。时间复杂度与上述第一种动态规划解法一样都是O(n²)但是由于空间复杂度是O(1)所以节省了空间,时间执行也比较快。
执行用时:36 ms, 在所有 Java 提交中击败了76.18%的用户
内存消耗:38.5 MB, 在所有 Java 提交中击败了81.75%的用户

class Solution {
    public String longestPalindrome(String s) {
        if(s==null || s.equals("")) return "";
        int len = 1;//长度
        int left = 0;//左边界
        int right = 0;//右边界
        for(int idx = 0;idx<s.length();idx++){
            int lbnd = idx-1;int rbnd = idx+1;//左边界右边界
            //两种方法向左右扩散
            //1.当前一个元素为中心
            while(lbnd>=0 && rbnd<s.length()){
                if(s.charAt(lbnd)!=s.charAt(rbnd)) break;
                else{
                    lbnd--;rbnd++;
                }
            }
            //比较长度
            if(rbnd-lbnd-1>len){
                len = rbnd-lbnd-1;
                left = (++lbnd);
                right = (--rbnd);
            }
            //2.当前两个元素为中心
            lbnd = idx-1;rbnd = idx;
            while(lbnd>=0 && rbnd<s.length()){
                if(s.charAt(lbnd)!=s.charAt(rbnd)) break;
                else{
                    lbnd--;rbnd++;
                }
            }//比较长度
            if(rbnd-lbnd-1>len){
                len = rbnd-lbnd-1;
                left = (++lbnd);
                right = (--rbnd);
            }
        }
        return s.substring(left,right+1);
    }
}

11 盛最多水的容器

题目描述:给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器。
题目地址:https://leetcode-cn.com/problems/container-with-most-water/
我的想法:(看题解的)一开始想的死动态规划,dp[i]=j代表从第i到第j块板所能围起来的水容积最大,但是不是很清楚状态转移方程该怎么表示。看了题解的双指针法,每次左右两边谁的值小谁就往中心移动。
执行用时:5 ms, 在所有 Java 提交中击败了36.42%的用户
内存消耗:52.2 MB, 在所有 Java 提交中击败了5.20%的用户

class Solution {
    public int getMin(int[] height,int x,int y){
        return height[x]>height[y]?y:x;
    }
    public int maxArea(int[] height) {
        if(height.length<=1) return 0;
        int left = 0,right = height.length-1;
        int volumn = height[getMin(height,left,right)]*(right-left);
        while(left!=right){
            if(height[left]<height[right])
                left++;
            else
                right--;
            int tmpv = height[getMin(height,left,right)]*(right-left);
            if(tmpv>=volumn) volumn = tmpv;
        }
        return volumn;
    }
}

148 排序链表

题目描述:给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
进阶:
你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
题目地址:https://leetcode-cn.com/problems/sort-list/
我的想法:单链表归并排序。下面是采用了递归做法,因为会使用栈所以空间复杂度是O(logn),仅作为了解但不符合题意。
执行用时:7 ms, 在所有 Java 提交中击败了78.34%的用户
内存消耗:46.7 MB, 在所有 Java 提交中击败了61.43%的用户

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    //合并两个有序的链表
    public ListNode merge(ListNode l1,ListNode l2){
        ListNode pre = new ListNode();//空表头统一操作
        ListNode head = pre;
        while(l1!=null && l2!=null){
            if(l1.val<l2.val){
                pre.next = l1;
                l1 = l1.next;
            }else{
                pre.next = l2;
                l2 = l2.next;
            }
            pre = pre.next;
        }
        //上述过程后可能存在某个链表还未迭代完
        if(l1!=null){
            pre.next = l1;
            l1 = l1.next;
        }
        if(l2!=null){
            pre.next = l2;
            l2 = l2.next;
        }
        ListNode tmp = head.next;
        head = null;
        return tmp;
    }
    //将链表拆分成前后各一半并返回中间结点
    public ListNode getMid(ListNode head){
        if(head==null || head.next==null) return head;
        ListNode slow = head,fast = head.next;
        while(fast!=null && fast.next!=null){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
    //排序
    public ListNode sort(ListNode head){
        if(head==null || head.next==null) return head;
        ListNode mid = getMid(head);//获取中间结点
        ListNode nbeg = mid.next;//后部分链表的起点
        mid.next = null;
        return merge(sort(head),sort(nbeg));
    }
    public ListNode sortList(ListNode head) {
        if(head==null || head.next==null) return head;
        ListNode pre_head = new ListNode();
        pre_head.next = head;
        head = sort(head);
        return head;
    }
}

推荐解法:归并排序的非递归版本,自底向上。
执行用时:10 ms, 在所有 Java 提交中击败了37.86%的用户
内存消耗:46.8 MB, 在所有 Java 提交中击败了42.35%的用户
之后再补上快排版本的单链表排序,这个也比较重要。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeList(ListNode l1,ListNode l2){
        ListNode pre = new ListNode();
        ListNode tmp = pre;
        while(l1!=null && l2!=null){
            if(l1.val<l2.val){
                tmp.next = l1;
                l1 = l1.next;
            }else{
                tmp.next = l2;
                l2 = l2.next;
            }
            tmp = tmp.next;
        }
        if(l1!=null){
            tmp.next = l1;
        }
        if(l2!=null){
            tmp.next = l2;
        }
        tmp = pre;
        pre = pre.next;
        tmp = null;
        return pre;
    }
    public ListNode sortList(ListNode head) {
        if(head==null || head.next==null) return head;
        int allLen = 0;//总长度
        ListNode tmp = head;
        while(tmp!=null){
            allLen++;
            tmp = tmp.next;
        }
        ListNode preHead = new ListNode(0,head);//空表头
        for(int subLen=1;subLen<allLen;subLen<<=1){
            //头结点前面的结点,头结点
            ListNode prev = preHead,curr = preHead.next;
            while(curr!=null){
                ListNode head1 = curr;//链1的头部
                for(int i=1;i<subLen && curr.next!=null;i++)
                    curr = curr.next;
                ListNode head2 = curr.next;//链2的头部
                curr.next = null;
                curr = head2;
                for(int i=1;i<subLen && curr!=null && curr.next!=null;i++)
                    curr = curr.next;
                ListNode next = null;
                if(curr!=null){
                    next = curr.next;
                    curr.next = null;
                }
                curr = next;
                ListNode merged = mergeList(head1,head2);//融合链1和链2
                prev.next = merged;
                while(prev.next!=null)
                    prev = prev.next;
            }
        }
        return preHead.next;
    }
}

仅做额外了解:快速排序版本代码如下,但是在这道题使用快排会超时,因为测试用例有有序表,所以会超时。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode quickSort(ListNode head){
        if(head==null || head.next==null) return head;
        //选取第一个结点作为基准
        ListNode pivot = head;
        ListNode dupHead = head;
        //从第二个结点开始遍历,遇到小于基准的放在基准前
        ListNode slow = head;
        ListNode fast = slow.next;
        while(fast!=null){
            //遇见>=pivot的结点继续遍历
            if(fast.val<pivot.val){
                //将<pivot的结点移到前面
                slow.next = fast.next;
                fast.next = dupHead;
                dupHead = fast;
                fast = slow.next;
            }else{
                slow = fast;
                fast = fast.next;
            }
        }
        //保证左侧有结点
        ListNode link1 = dupHead;
        if(dupHead!=pivot){
            //将左侧链条断掉
            while(link1!=null && link1.next!=pivot)
                link1 = link1.next;
            link1.next = null;
        }
        //保证右侧有结点
        ListNode link2 = pivot.next;
        if(pivot.next!=null){
            //将右侧链条断掉
            pivot.next = null;
        }
        //对pivot左侧进行排序
        ListNode leftLink = null;
        if(dupHead!=pivot)
            leftLink = quickSort(dupHead);
        //对pivot右侧进行排序
        ListNode rightLink = null;
        if(pivot!=null)
            rightLink = quickSort(link2);
        //两条链结合
        if(leftLink!=null){
            link1 = leftLink;
            while(link1!=null && link1.next!=null)
                link1 = link1.next;
            link1.next = pivot;
            pivot.next = rightLink;
            return leftLink;
        }else{
            pivot.next = rightLink;
            return pivot;
        }
    }
    public ListNode sortList(ListNode head) {
        if(head==null || head.next==null) return head;
        return quickSort(head);
    }
}

3 无重复字符的最长子串

题目描述:给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
题目地址:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
我的想法:用一个HashMap记录每一种字符及其最近出现所在的下标位置,对于数组dp来说dp[i]=j代表第i个字符到第j个字符组成的子字符串不出现重复字符,遇到重复字符时更新HashMap的value。
执行用时:9 ms, 在所有 Java 提交中击败了40.75%的用户
内存消耗:38.7 MB, 在所有 Java 提交中击败了39.66%的用户

class Solution {
    public int lengthOfLongestSubstring(String s) {
        if(s==null || s.equals("")) return 0;
        int[] dp = new int[s.length()];
        Map<Character,Integer> map = new HashMap<>();//key:字符,value:索引位置
        dp[0] = 0;
        map.put(s.charAt(0),0);
        for(int i=1;i<s.length();i++){
            if(map.get(s.charAt(i))!=null && map.get(s.charAt(i))>=dp[i-1]){//前面已存在字符
                dp[i] = map.get(s.charAt(i)) + 1;
            }else{
                dp[i] = dp[i-1];//无重复字符
            }
            map.put(s.charAt(i),i);//替换
        }
        int maxn = 0;
        for(int i=0;i<s.length();i++)
            if((i-dp[i]+1)>maxn) maxn = i-dp[i]+1;
        return maxn;
    }
}

15 三数之和

题目描述:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
题目地址:https://leetcode-cn.com/problems/3sum/
我的想法:先对数组排序,固定一个数字后采用双指针查找另外两个数字。注意双指针需要跳过重复的数字元素,保证加入list的元素不重复。(时空消耗有点高)
执行用时:28 ms, 在所有 Java 提交中击败了36.04%的用户
内存消耗:43.5 MB, 在所有 Java 提交中击败了5.51%的用户

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> list = new LinkedList<>();
        if(nums.length<3) return list;
        Arrays.sort(nums);
        //先固定一个数
        for(int i=0;i<nums.length-2;i++){
            if(i>0 && nums[i]==nums[i-1]) continue;
            int pivot = nums[i];
            int left = i+1,right = nums.length-1;//双指针
            if(left>=nums.length || right<0) continue;//指针越界
            while(left<right){
                int value = pivot + nums[left] + nums[right];
                if(value>0){//右指针左移
                    right--;
                    while(right>i && nums[right]==nums[right+1]) right--;
                }
                else if(value<0){
                    left++;
                    while(left<nums.length && nums[left]==nums[left-1]) left++;
                }
                else{
                    List<Integer> tmp = new LinkedList<>();
                    tmp.add(pivot);tmp.add(nums[left]);tmp.add(nums[right]);
                    list.add(tmp);
                    right--;
                    while(right>i && nums[right]==nums[right+1]) right--;
                    left++;
                    while(left<nums.length && nums[left]==nums[left-1]) left++;
                }
            }
        }
        return list;
    }
}

15 三数之和

题目描述:给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
题目地址:https://leetcode-cn.com/problems/add-two-numbers/
我的想法:从头到尾相加,注意进位判断,以及最末尾是否还存在进位,有的话需要再插入一个1。
执行用时:2 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:39 MB, 在所有 Java 提交中击败了24.52%的用户

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        if(l1==null) return l2;
        if(l2==null) return l1;
        ListNode head = new ListNode(0);
        ListNode pre = head;
        int carry = 0;//进位
        while(l1!=null || l2!=null){
            int val1 = l1==null?0:l1.val;
            int val2 = l2==null?0:l2.val;
            if(l1!=null) l1 = l1.next;
            if(l2!=null) l2 = l2.next;
            int sum = carry + val1 + val2;
            ListNode node = new ListNode();
            if(sum>=10){
                node.val = sum - 10;
                carry = 1;
            }else{
                node.val = sum;
                carry = 0;
            }
            pre.next = node;
            pre = node;
        }
        if(carry==1){
            ListNode node = new ListNode(1);
            pre.next = node;
        }
        return head.next;
    }
}

46 全排列

题目描述:给定一个 没有重复 数字的序列,返回其所有可能的全排列。
题目地址:https://leetcode-cn.com/problems/permutations/
我的想法:dfs。
执行用时:2 ms, 在所有 Java 提交中击败了52.73%的用户
内存消耗:39 MB, 在所有 Java 提交中击败了5.23%的用户

class Solution {
    private boolean[] vis;
    public void dfs(List<List<Integer>> list,List<Integer> vec,int idx,int[] nums){
        if(vis[idx]) return ;//已访问过
        vis[idx] = true;
        vec.add(nums[idx]);//加入list
        if(vec.size()==nums.length){
            list.add(new LinkedList(vec));//加入list
        }else
            for(int i=0;i<nums.length;i++)
                dfs(list,vec,i,nums);
        vec.remove(vec.size()-1);//去除
        vis[idx] = false;
    }
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> list = new LinkedList<>();
        if(nums.length==0) return list;
        vis = new boolean[nums.length];
        List<Integer> vec = new LinkedList<>();
        for(int i=0;i<nums.length;i++)
            dfs(list,vec,i,nums);
        return list;
    }
}

6 Z 字形变换

题目描述:将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:
P        A       H       N
A   P   L   S  I     I  G
Y        I        R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
题目地址:https://leetcode-cn.com/problems/zigzag-conversion/
我的想法:做这种题我个人觉得需要在纸上画出来才能找规律。关于解法应该有两种:
① 第一种是声明一个二维数组然后填充字符再每一行依次添加进字符串;
② 第二种解法是基于找规律的。
首先第一行和最后一行,这两行的字符索引间隔是2*(n-1),比如当行数为4时,第一行的字符索引就是0,6,12,18…,最后一行就是3,9,15,…,它们的间隔都是2*(4-1)=6。
对于中间的所有行,举个例子来说,当总行数为4时,对于第1行,索引分别是1,5,7,11,13,17,19…,可以发现5=1+2*(3-1),7=5+2*(1-0),11=7+2*(3-1),13=11+2*(1-0),…。这样一次添加进StringBuffer里就能得到字符串。
执行用时:3 ms, 在所有 Java 提交中击败了97.99%的用户
内存消耗:38.6 MB, 在所有 Java 提交中击败了86.50%的用户

class Solution {
    public String convert(String s, int numRows) {
        if(numRows==1) return s;
        StringBuffer sb = new StringBuffer();
        int limit = s.length();//字符串长度
        int ceil = 0,floor = numRows-1,gap = 2*numRows-2;//最小的行数,最大的行数,最顶行最底行的间隙
        //最顶行的字符
        for(int i=0;(ceil+i*gap)<limit;i++)
            sb.append(s.charAt(ceil+i*gap));
        for(int i=1;i<numRows-1;i++){
            int idx = i;
            while(idx<limit){
                sb.append(s.charAt(idx));
                idx += 2*(floor-i);
                if(idx<limit)
                    sb.append(s.charAt(idx));
                idx += 2*(i-ceil);
            }
        }
        //最低行的字符
        for(int i=0;(floor+i*gap)<limit;i++)
            sb.append(s.charAt(floor+i*gap));
        return sb.toString();
    }
}

56 合并区间

题目描述:以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
题目地址:https://leetcode-cn.com/problems/merge-intervals/
我的想法:快排(按照每一个区间的左界)+双指针(注意判断第i个区间的右界是否大于等于第i+1个区间的左界)。
执行用时:3 ms, 在所有 Java 提交中击败了98.52%的用户
内存消耗:41 MB, 在所有 Java 提交中击败了84.05%的用户

class Solution {
    //根据区间左值进行排序
    public void quickSort(int[][] intervals,int left,int right){
        if(left>=right) return ;
        int i = left,j = right;
        int pivot_left = intervals[left][0];
        int pivot_right = intervals[left][1];
        while(i<j){
            while(i<j && intervals[j][0]>=pivot_left) j--;
            if(i<j && intervals[j][0]<pivot_left){
                intervals[i][0] = intervals[j][0];
                intervals[i][1] = intervals[j][1];
                i++;
            }
            while(i<j && intervals[i][0]<=pivot_left) i++;
            if(i<j && intervals[i][0]>pivot_left){
                intervals[j][0] = intervals[i][0];
                intervals[j][1] = intervals[i][1];
                j--;
            }
        }
        intervals[i][0] = pivot_left;
        intervals[i][1] = pivot_right;
        quickSort(intervals,left,i-1);
        quickSort(intervals,i+1,right);
    }
    public int[][] merge(int[][] intervals) {
        int rows = intervals.length;
        if(rows<=1) return intervals;
        quickSort(intervals,0,rows-1);//按照区间左值快速排序
        List<Integer[]> list = new ArrayList<>();
        int i = 0,j = 0;
        while(i<rows && j<rows){
            //找当前区间的下界
            int left = i+1;//左指针
            while(left<rows && intervals[left][0]==intervals[i][0]) left++;
            left--;
            //找当前区间的上界
            int right = j;
            while(right+1<rows && intervals[j][1]>=intervals[right+1][0]){
                right++;
                if(intervals[right][1]>=intervals[j][1]) j = right;
            }
            list.add(new Integer[]{intervals[left][0],intervals[j][1]});
            if(left<right) i = j = right+1;
            else i = j = left+1;
        }
        int[][] res = new int[list.size()][2];
        for(int idx=0;idx<list.size();idx++){
            res[idx][0] = (int)list.get(idx)[0];
            res[idx][1] = (int)list.get(idx)[1];
        }
        return res;
    }
}

31 下一个排列

题目描述:实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。
题目地址:https://leetcode-cn.com/problems/next-permutation/
我的想法:从最末尾搜索一个以最后元素为结尾的最长的非减序列,然后交换这个序列边界左边的值和这个序列里最大的小于等于这个边界左边的值的值(有点难表达),再倒置这个非减序列。
执行用时:1 ms, 在所有 Java 提交中击败了98.29%的用户
内存消耗:38.5 MB, 在所有 Java 提交中击败了76.66%的用户

class Solution {
    public void swap(int[] nums,int left,int right){
        int mid = (left+right)/2;
        while(left<=right){
            int tmp = nums[left];
            nums[left] = nums[right];
            nums[right] = tmp;
            left++;right--;
        }
    }
    public void nextPermutation(int[] nums) {
        int len = nums.length;
        if(len<=1) return ;
        int right = len-1;
        int left = right;
        //找到以最后一个元素为结尾的最长非增序列
        left = right;
        while(left>0 && nums[left]<=nums[left-1]) left--;
        if(left==0){
            swap(nums,0,right);return ;
        }
        //找到最小的小于等于该序列左边的值
        int bnd = right;
        while(bnd>=left && nums[bnd]<=nums[left-1]) bnd--;
        //交换
        int tmp = nums[bnd];
        nums[bnd] = nums[left-1];
        nums[left-1] = tmp;
        swap(nums,left,right);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值