力扣1-200刷题总结(4/5)

链表问题

从指定位置反转链表

92题

 要反转这个链表我们至少需要以下几个变量:

  • pre:反转链表段的前一个节点
  • cur:反转链表的头

在while循环中,next先指向还没有发生反转的链表的下一个节点,所以最后next会保存5的位置

 public ListNode reverseBetween(ListNode head, int left, int right) {
        if(left==right)return head;
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pre=dummy;
        ListNode cur=dummy.next;
        //移动left次,使得pre对应于反转链表段的前一个
        for (int i = 0; i < left-1 ; i++) {
            pre=pre.next;
            cur=cur.next;
        }
        ListNode subDummy=pre;//保存用于连接链表段
        ListNode subHead=cur;//保存用于后续连接尾部
        //后移准备操作链表反转
        pre=pre.next;
        cur=cur.next;
        //此操作对right-left格链表执行
        ListNode next=null;
        for (int i = 0; i < right - left; i++) {
            next=cur.next;
            cur.next=pre;
            pre=cur;
            cur=next;
        }
        subDummy.next=pre;
        subHead.next=next;

        return dummy.next;
    }

分隔链表

86题

 搜索每个节点,分别保存小于该节点的链表头和大于该节点的链表头,每当有节点小于x就加在smallHead的后面,反之亦然,最后需要注意的是large.next需要断开,因为如图上述情况5并不是尾结点,不断开的话会形成环

public ListNode partition(ListNode head, int x) {
       ListNode small=new ListNode(0);
       ListNode smallHead=small;
       ListNode large=new ListNode(0);
       ListNode largeHead=large;
       while (head!=null){
           if(head.val<x){
               small.next=head;
               small=small.next;
           }else{
               large.next=head;
               large=large.next;
           }
           head=head.next;
       }
       large.next=null;
       small.next=largeHead.next;
       return smallHead.next;
    }

删除排序链表中的重复元素

82题

使用pre记录一个前置节点,当重复节点出现的时候让cur不停的往后移动,仅当pre和cur中间没有节点的时候移动pre的位置,不然就讲重复的节点直接删除

public ListNode deleteDuplicates(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode dummy=new ListNode(0);
        dummy.next=head;
        ListNode pre=dummy;
        ListNode cur=head;
        while (cur!=null){
            while (cur.next!=null&&cur.val==cur.next.val){
                cur=cur.next;
            }
            //pre和cur之间没有重复的节点
            if(pre.next==cur){
                pre=pre.next;
            }else{
                pre.next=cur.next;//删除重复节点
            }
            //遍历下一个节点
            cur=cur.next;
        }
        return dummy.next;

    }

旋转链表

61题

 先将链表连成一个环,然后再指定的地方断开,其中移动的次数为n-k,由于k可能大于n所以要对k进行取模运算,移动后iter位于头的前一个位置(也就是当前环的尾部),因为iter开始是从尾部开始移动的,

    //先把链表变成环,再找断开的地方就行了
    public ListNode rotateRight(ListNode head, int k) {
        //特殊情况排除
        if (k == 0 || head == null || head.next == null) {
            return head;
        }
        //记录链表长度
        int n = 1;
        ListNode iter = head;
        while (iter.next != null) {
            iter = iter.next;
            n++;
        }
        //需要移动的次数
        int add = n - k % n;
        //如果刚好为n说明不需要移动,直接return就好了
        if (add == n) {
            return head;
        }
        //链表成环,当前iter位于链表的尾部
        iter.next = head;
        //移动到环的尾部
        while (add-- > 0) {
            iter = iter.next;
        }
        //记录链表的头
        ListNode ret = iter.next;
        //尾部指向null
        iter.next = null;
        return ret;
    }

删除链表的倒数第N个节点

19题

使用快慢指针解决此问题,先用fast指针探路,如果在n次位移以后fast已经是null了说明n>链表的总长度,说明需要删除头节点,此时直接return head.next,此题难点就在于此,需要将头结点删除的情况单独分离开来

如果不为null,fast指针和slow指针一起走,当fast指针的下一个为null的时候slow正处于将要被删除节点的前一个节点,

public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode fast=head,slow=head;
        while(n!=0){
            fast=fast.next;
            n--;
        }
        if(fast==null)return head.next;
        while (fast.next!=null){
            fast=fast.next;
            slow=slow.next;
        }
        slow.next=slow.next.next;
        return head;
    }

合并两个有序链表

21题

 用递归来做的话代码更为简洁

//给定两个链表返回最小的头
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        //两个函数都为空的时候俩表合并完成
        if(l1==null)return l2;
        else if(l2==null) return l1;
        //判断当前哪个头结点更小,使用较小的头结点next指针指向其余节点的合并结果
        else if(l1.val<l2.val){
            l1.next=mergeTwoLists(l1.next,l2);
            return l1;
        }else{
            l2.next=mergeTwoLists(l1,l2.next);
            return l2;
        }
    }

合并k个升序的链表

23题

使用一个优先队列来帮助我们做就很简单了,把所有的节点都丢进优先队列里去然后重新组装链表就行了

 public ListNode mergeKLists(ListNode[] lists) {
      if(lists==null||lists.length==0)return null;
        PriorityQueue<ListNode> pq = new PriorityQueue<>(new Comparator<ListNode>() {
            @Override
            public int compare(ListNode l1, ListNode l2) {
                return l1.val - l2.val;
            }
        });
        for (int i = 0; i < lists.length; i++) {
            ListNode now=lists[i];
            while (now!=null){
                pq.offer(now);
                now=now.next;
            }
        }
        ListNode last;
        ListNode res;
        if(pq.size()>=1){
            last = pq.poll();
            res =last;
        }else{
            return null;
        }
       
        while (!pq.isEmpty()) {
            ListNode poll = pq.poll();
            last.next=poll;
            last=poll;
        }
        last.next=null;
        return res;
    }

两两交换链表中的节点

24题

 终止条件为,只有一个节点或者没有节点,swapPairs这个函数会返回链表交换后节点的头

//给定一个链表,返回交换完成的子链表
    public ListNode swapPairs(ListNode head) {
        //递归的终止条件,只有一个节点或者没有节点无法完成交换
        if (head == null || head.next == null) {
            return head;
        }
        ListNode one = head;
        ListNode two = one.next;
        ListNode three = two.next;

        two.next = one;
        one.next = swapPairs(three);
        return two;
    }

k个一组翻转链表

25题

 pre:维护成需要翻转链表的头节点的上一个节点

end:维护成需要翻转链表的尾结点(翻转后会变成头节点)--当end==null的时候说明不足k次直接跳出循环不处理就行

next:维护成end翻转前的后一个节点用于后续连接链表

start:维护成翻转链表的头节点(翻转后变成了尾节点)--没轮循环开始将pre和end指向start因为它也是下一段该翻转链表的前一个节点

 public ListNode reverseKGroup(ListNode head, int k) {
        if(head==null||head.next==null)return head;
        ListNode dummy=new ListNode(0);//头结点前的节点方便返回操作
        dummy.next=head;
        ListNode pre=dummy;//以后会指向每次要翻转的链表段头结点的上一个节点。
        ListNode end=dummy;//以后会指向每次要翻转的链表的尾节点
        while(end.next!=null){
            for (int i = 0; i < k &&end!=null; i++) {//循环k次找到end的位置,k=2的时候end往后移动2次
                end=end.next;
            }
            //end==null说明翻转的链表节点数小于k
            if(end==null){
                break;
            }
            //记录end.next方便后面连接链表
            ListNode next=end.next;
            //断开链表
            end.next=null;
            //记录要翻转的链表的头结点
            ListNode start=pre.next;
            //翻转链表 dummy-1-2 -> dummy-2-1
            pre.next=reverse(start);
            //翻转后我们需要把头结点编导最后把断开的链表重新连接
            start.next=next;
            //将pre缓存下次要翻转的链表的头结点的上一个节点
            pre=start;
            //翻转结束,将end变为下次要翻转的链表的头结点的上一个节点,即start
            end=start;
        }
        return dummy.next;
    }
    public ListNode reverse(ListNode head){
        //单链表为空或者只有一个节点,直接返回
        if(head==null||head.next==null)return head;
        //前一个节点指针
        ListNode preNode=null;
        //当前节点指针
        ListNode curNode= head;
        //下一个节点指针
        ListNode nextNode=null;
        while(curNode!=null){
            nextNode=curNode.next;//nextNode指向下一个节点,保存当前节点后面的链表
            curNode.next=preNode;//将当前节点next指向前一个节点
            preNode=curNode;//pre指针向后移动,指向当前节点
            curNode=nextNode;//cur向后移动,指向后一个节点
        }
        return preNode;
    }

解码方法

91题

 此题也是用动态规划做,当前导为0的时候,s是无解的

一个字符一个字符的读,我们默认每个状态都是可编译的,所以一开始先继承上次的状态last_1

  • 当当前数字为0的时候如果前驱是1或者2,由于它只能是10或者20了所以需要往前移动两次,使用last_2来记录,否则无解
  • 当数前驱数字为1时,当前数字可以为任意值
  • 当前驱数字为2时,当前数字只能是1-6的一个,该情况与1的情况一致,数字可以拆分成两个单独的或者一个整体,因为我们默认temp继承了last_1,所以它已经继承了单独的状态,只要将last_2合并的状态加上来就行了
int numDecodings(string s) {
        if (s[0] == '0') return 0;
        vector<int> dp(s.size()+1);
        dp[0]=1;dp[1]=1;
        for (int i =1; i < s.size(); i++) {
            if (s[i] == '0')//1.s[i]为0的情况
                if (s[i - 1] == '1' || s[i - 1] == '2') //s[i - 1]等于1或2的情况
                    dp[i+1] = dp[i-1];//由于s[1]指第二个下标,对应为dp[2],所以dp的下标要比s大1,故为dp[i+1]
                else 
                    return 0;
            else //2.s[i]不为0的情况
                if (s[i - 1] == '1' || (s[i - 1] == '2' && s[i] <= '6'))//s[i-1]s[i]两位数要小于26的情况
                    dp[i+1] = dp[i]+dp[i-1];
                else//其他情况
                    dp[i+1] = dp[i];
        }
        return dp[s.size()];
    }

 复杂度简化

public int numDecodings(String s) {
        if(s.charAt(0) == '0') {
            //说明无解
            return 0;
        }
        char[] charArray = s.toCharArray();
        int last_2 = 1, last_1 = 1;  //last_2 代表i-2  last_1 代表i-1 temp 代表当前
        for(int i=1; i<s.length(); i++) {
            int temp = last_1;//正常情况就是继承上一次的状态,我们默认它为可编译的

            if(charArray[i] == '0') {
                //如果是10这种情况,相当于减去当前的两个字符,继承i-2的状态
                if(charArray[i-1] == '1' || charArray[i-1] == '2') {
                    temp = last_2;
                }
                //30这种情况也是无解的
                else {
                    return 0;
                }
            }else if( charArray[i-1] == '1' || (charArray[i-1] == '2' && charArray[i] - '0'>0 && charArray[i] - '0'<7)) {
                temp += last_2;//现在构成了12这样的状态,他可以拆分成1 2 和12两种,temp当前为last_1已经包含了1 2的拆分的状态,还缺少12的合并状态,所以从i-2继承
            }
            //状态更新
            last_2 = last_1;
            last_1 = temp;
        }
        return last_1;
    }

合并两个有序的数组

88题

 从后向前开始合并,这样的题不要用for循环做,用多个指针就很简单

public void merge(int[] nums1, int m, int[] nums2, int n) {
        int index = m + n;
        while (n>0){
            if(m>0&&nums1[m-1]>nums2[n-1]){
                nums1[--index]=nums1[--m];
            }else{
                nums1[--index]=nums2[--n];
            }
        }
    }

最大的矩形

柱状图中的最大矩形

84题

 这样边界不好判断的题目最好加入一个哨兵,这样就可以避免一些非空的判断,在筛除完特殊情况之后,对此类的题的数组进行一个修改使其变成0 ,原数组,0的形式

对于java的数组复制,一共有四个api

object类的clone方法

Arrays的copyOfRange() 方法:第2,3个参数是from,to左开右闭

int[] original = new int[]{1, 2, 3, 4, 5};
int[] dest1 = Arrays.copyOfRange(original, 0, 8);//12345000
int[] dest2 = Arrays.copyOfRange(original, 1, 4);//234

Arrays的copyOf()方法,第二个参数是数组长度

int[] original = new int[]{1, 2, 3, 4, 5};
int[] dest1 = Arrays.copyOf(original, 8);//12345000
int[] dest2 = Arrays.copyOf(original, 3);//123

System的arraycopy方法,这是唯一可以修改数组前导的方法,其参数为:

原始数组.原始数组开始复制的位置,目标数组,目标数组的初始位置,复制的长度

int[] src = new int[]{1, 2, 3};
int[] des = new int[]{1, 2, 3, 0, 0, 0, 0};
System.arraycopy(src, 0, des, 3, 3);//1, 2, 3, 1, 2, 3, 0

在此题中我们发现最大长方形的面积只在高度减少的时候计算,比如高度为5的最大长方形要在6的地方计算为5*2;我们向栈中存放索引,因为索引既可以拿到柱状图的高度又可以拿到柱状图的宽度,值得注意的是这个栈还是一个索引的单调栈,因此宽度永远不可能为负数,在主要逻辑运行之前,向栈中存一个0索引

在主要逻辑运行的时候,不停的向栈中压入新数组的值,当当前遍历的值要小于栈顶的值的时候开始计算矩形的长度,由于我们是从低到高记录的,所以在往回推算面积的时候只需要取出该层的高度并且求出索引差(也就是宽度),就可以得到面积

在栈中存在的倒数第二个值是索引高度为1的索引下标,由于它是最矮的值,所以它需要计算全部的长度,这也是在数组中存放前导和后导0的原因

我们在栈中抛出的都是高度比当前i更高的索引值(或者说每次计算完一次面积,就把该高度从栈中一移除),所以不用担心值抛出去但是面积计算错误的情况,举个例子 在计算高度为2的索引的时候,高度为5和6的索引已经被抛出去了,但是这并不对2的面积计算造成影响,因为它只需要找到高度为1的索引位置计算面积就行了

public int largestRectangleArea(int[] heights) {
        int len = heights.length;
        //特殊情况
        if (len == 0) {
            return 0;
        }

        if (len == 1) {
            return heights[0];
        }

        int res = 0;

        int[] newHeights = new int[len + 2];
        newHeights[0] = 0;
        System.arraycopy(heights, 0, newHeights, 1, len);
        newHeights[len + 1] = 0;
        len += 2;
        heights = newHeights;//新数组 0 原数组 0

        Deque<Integer> stack = new ArrayDeque<>(len);
        // 先放入哨兵,在循环里就不用做非空判断
        stack.addLast(0);//栈中存的是索引
        //这一段的总体逻辑是栈里面只存递增高度的下标,遇到比自己小的就开始往前计算以自己为高度的每个矩形的面积
        for (int i = 1; i < len; i++) {
            //遍历到的高度比当前栈中高度要小的时候开始计算矩形
            while (heights[i] < heights[stack.peekLast()]) {
                int curHeight = heights[stack.pollLast()]; //取出高度
                int curWidth = i - stack.peekLast() - 1; //计算长度
                res = Math.max(res, curHeight * curWidth);
            }
            stack.addLast(i);
        }
        return res;
    }

二维数组中的最大矩形

85题

 对每一层构造一个数组,题目其实就和上一题一样,由于是逐层遍历下来的不必担心有悬在空中的情况,因为这种情况会被上一层计算到

public int maximalRectangle(char[][] matrix) {
        int m=matrix.length;
        if(m==0)return 0;
        int n=matrix[0].length;
        int[] heights = new int[n];
        int maxArea=0;
        //从上到下遍历构造出高度数组
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if(matrix[i][j]=='1'){
                    heights[j]+=1;
                }else{
                    heights[j]=0;
                }
            }
            maxArea=Math.max(maxArea,largestRectangleArea(heights));
        }
        return maxArea;

    }
    public int largestRectangleArea(int[] heights) {
        int len = heights.length;
        //特殊情况
        if (len == 0) {
            return 0;
        }

        if (len == 1) {
            return heights[0];
        }

        int res = 0;

        int[] newHeights = new int[len + 2];
        newHeights[0] = 0;
        System.arraycopy(heights, 0, newHeights, 1, len);
        newHeights[len + 1] = 0;
        len += 2;
        heights = newHeights;//新数组= 0+原数组+0

        Deque<Integer> stack = new ArrayDeque<>(len);
        // 先放入哨兵,在循环里就不用做非空判断
        stack.addLast(0);//栈中存的是索引
        //这一段的总体逻辑是栈里面只存递增高度的下标,遇到比自己小的就开始往前计算以自己为高度的每个矩形的面积
        for (int i = 1; i < len; i++) {
            //遍历到的高度比当前栈中高度要小的时候开始计算矩形
            while (heights[i] < heights[stack.peekLast()]) {
                int curHeight = heights[stack.pollLast()]; //取出高度
                int curWidth = i - stack.peekLast() - 1; //计算长度
                res = Math.max(res, curHeight * curWidth);
            }
            stack.addLast(i);
        }
        return res;
    }

删除有序数组中的重复项

80题,每个元素最多出现两次

 我们使用一个通用的解法,无论以后只能出现3次还是4次都可以用此方法求解;

举个例子对于0011112222来说,如果只保留两个数字的话,算法会如此运行

首先保留00,而后对于1与之前的0不同,所以保留,但是遍历到第三个1的时候因为第三个1的两个数之前也是1,所以不保留故而得001122

public int removeDuplicates(int[] nums) {
        return process(nums, 2);
    }
    //通用解法
    int process(int[] nums, int k) {
        int u = 0;
        for (int x : nums) {
            //对于前k个数字我们可以直接保留
            //对于k个数字以后的数字与k个数字以前的数字比较,不同则保留
            if (u < k || nums[u - k] != x) nums[u++] = x;
        }
        return u;
    }

快速排序的子过程

75题,颜色分类

 其实就是利用zero和two两个指针,在遍历过程中遍历到0就与zero指针所在的值交换,遍历到2就与two指针所在的值交换,zero指针一致加,two指针一直减少,当i和two指针相等的时候说明遍历已经完成;

需要注意的是0和1一定在2的前面,所以nums[i]=0的时候要丢到前面去,前面已经是排序好的值,不需要再次操作,所以i++,nums[i]=1的时候,当前的值不需要改变,因为它迟早会被0挤下来,所以直接遍历下一个数字;,而遍历到2的时候是要把2丢到后面去的,丢到后面去以后,当前位置会进来一个新的值,所以不需要i++;

 public void sortColors(int[] nums) {
        int len = nums.length;
        if (len < 2) {
            return;
        }

        // all in [0, zero) = 0
        // all in [zero, i) = 1
        // all in [two, len - 1] = 2

        // 循环终止条件是 i == two,那么循环可以继续的条件是 i < two
        // 为了保证初始化的时候 [0, zero) 为空,设置 zero = 0,
        // 所以下面遍历到 0 的时候,先交换,再加
        int zero = 0;

        // 为了保证初始化的时候 [two, len - 1] 为空,设置 two = len
        // 所以下面遍历到 2 的时候,先减,再交换
        int two = len;
        int i = 0;
        // 当 i == two 上面的三个子区间正好覆盖了全部数组
        // 因此,循环可以继续的条件是 i < two
        while (i < two) {
            if (nums[i] == 0) {
                swap(nums, i, zero);
                zero++;
                i++;
            } else if (nums[i] == 1) {
                i++;
            } else {
                two--;
                swap(nums, i, two);
            }
        }
    }
    private void swap(int[] nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }

搜索二维矩阵

74题

从每行的第一个数字可以大概确定target的范围,从最后一行开始搜索,如果第一个数字比当前数字大就减少行数,否则增加列数

 public boolean searchMatrix(int[][] matrix, int target) {
        int rows = matrix.length - 1, columns = 0;
        while (rows >= 0 && columns < matrix[0].length) {
            int num = matrix[rows][columns];
            if (num == target) {
                return true;
            } else if (num > target) {
                rows--;
            } else {
                columns++;
            }
        }
        return false;
    }

矩阵置零

73题

 由于要把零所在的行和列都置为9,所以我们先进行一次遍历,把所有有0的行和列都标记出来,使用第一行和第一列来标志,但是要注意排查第一行和第一列有零的情况

public void setZeroes(int[][] matrix) {
        int row = matrix.length;
        int col = matrix[0].length;
        boolean row0_flag = false;
        boolean col0_flag = false;
        // 第一行是否有零
        for (int j = 0; j < col; j++) {
            if (matrix[0][j] == 0) {
                row0_flag = true;
                break;
            }
        }
        // 第一列是否有零
        for (int i = 0; i < row; i++) {
            if (matrix[i][0] == 0) {
                col0_flag = true;
                break;
            }
        }
        // 把第一行第一列作为标志位
        for (int i = 1; i < row; i++) {
            for (int j = 1; j < col; j++) {
                if (matrix[i][j] == 0) {
                    matrix[i][0] = matrix[0][j] = 0;
                }
            }
        }
        // 置0
        for (int i = 1; i < row; i++) {
            for (int j = 1; j < col; j++) {
                if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                    matrix[i][j] = 0;
                }
            }
        }
        if (row0_flag) {
            for (int j = 0; j < col; j++) {
                matrix[0][j] = 0;
            }
        }
        if (col0_flag) {
            for (int i = 0; i < row; i++) {
                matrix[i][0] = 0;
            }
        }

简化路径

71题

关于路径的题目可以用一个栈来模拟,预先使用/来分割每个路径串,..其实是返回上一个路径,其实也就是出栈嘛,如果不是..那就直接入栈呗,最后把栈里的每个元素拿出来加上/不就成了嘛;注意这里的stack的遍历方式,因为是倒着添加的,所以需要从后往前拿

 public String simplifyPath(String path) {
        Deque<String> stack = new LinkedList<>();
        StringBuilder ret = new StringBuilder();
        for (String p : path.split("/")) {
            if (!stack.isEmpty() && p.equals("..")) {
                stack.removeFirst();
            } else if (!" ..".contains(p)) {
                stack.push(p);
            }
        }
        while (!stack.isEmpty()){
            ret.append("/"+stack.removeLast());
        }
        return ret.length() == 0 ? "/" : ret.toString();
    }

寻找平方根

69题

刨除掉特殊情况1和0,x的平方根在1-1/2*x之间 ,随意我们可以用二分法在这个范围内筛选结果,筛选过程中有可能会出现整形溢出情况,改用除法运算,由于最后的结果是要靠近小的那个数(2.2约成2),所以将压缩左边界的情况和等于的情况放在一起,然后返回left

public int mySqrt(int x) {
        // 特殊值判断
        if (x == 0) {
            return 0;
        }
        if (x == 1) {
            return 1;
        }

        int left = 1;
        int right = x / 2;
        // 在区间 [left..right] 查找目标元素
        while (left < right) {
            int mid = left + (right - left + 1) / 2;
            // 注意:这里为了避免乘法溢出,改用除法
            if (mid > x / mid) {
                // 下一轮搜索区间是 [left..mid - 1]
                right = mid - 1;
            } else {
                // 下一轮搜索区间是 [mid..right]
                left = mid;
            }
        }
        return left;
    }

文本左右对齐算法

68题

 最后一行要做特殊处理,将所有单词压缩在左侧,空格全部放在右侧,最后一行的单词之间最多只能有一个空格

findRight函数给定一个left作为起始单词,找到最多的符合maxWidth的单词下标

fillWord函数给定一个left和right索引,向这些单词中间填充空格来达到刚好maxWidth的长度,算法中先算出一共需要添加的空格数量,由于每个单词的末尾需要带一个空格(我们默认每个单词后都带了一个空格),但是最后一个单词的末尾不需要空格,对于maxWidth为10,拥有两个单词,第一个单词长度为2,第二个单词长度为3,它需要添加的空格数量为10+1-2-2-3=4;由于该行只有两个单词,它的间隙数spaceAVG=1,所有空格都填充在中间;

以上只是一个简单的例子,该题最离谱的要求在于尽可能均匀分配单词间的空格数量。如果某一行单词间的空格不能均匀分配,则左侧放置的空格数要多于右侧的空格数。也就是说如果有4个单词,三个间隙,要存放10个空格的话它只能是4,3,3这样存放,可以这样做10/3=3--1,我们每次计算当前的位置的索引与left的差值如果该差值小于余数就让他增加一个空格;其实这样做事很有道理的,如果一旦有余数,那他的第一个空隙一定会增加一个空格,如果每个空隙都增加一个空格肯定会大于能增加的空格总数,所以使用下标与余数的关系来确定是否需要增加空格

List<String> resList=new ArrayList<>();
    public List<String> fullJustify(String[] words, int maxWidth) {
        int left=0,lenW=words.length;
        while(left<lenW){
            int right=findRight(words,maxWidth,left);
            if(right==lenW-1){
                resList.add(fillWords(words,maxWidth,left,right,true));
            }else{
                resList.add(fillWords(words,maxWidth,left,right,false));
            }
            left=right+1;
        }
        return resList;
    }
    public String fillWords(String[] words,int maxWidth,int left,int right,boolean isLastLine){
        int wordNums=right-left+1;
        // 除去每个单词尾部空格, + 1 是最后一个单词的尾部空格的特殊处理
        int spaceCount=maxWidth+1-wordNums;
        for (int i=left;i<=right;i++) {
            spaceCount -= words[i].length();  // 除去所有单词的长度
        }

        int spaceSuffix=1;    // 词尾空格
        int spaceAvg= (wordNums==1)? 1:spaceCount/(wordNums-1);  // 额外空格的平均值
        int spaceExtra= (wordNums==1)? 0:spaceCount%(wordNums-1);// 额外空格的余数

        //String ans="";
        StringBuilder sb=new StringBuilder();
        for (int i=left;i<right;i++) {
            sb.append(words[i]);// 填入单词
            if (isLastLine){   // 特殊处理最后一行
                sb.append(" ");
                continue;
            }
            int sum=spaceSuffix + spaceAvg+ ((i-left)<spaceExtra?1:0);
            while(sum-->0){sb.append(" ");}// 根据计算结果补上空格
            //fill_n(back_inserter(ans), spaceSuffix + spaceAvg + ((i - bg) < spaceExtra), ' ');
        }
        sb.append(words[right]);// 填入最后一个单词
        int sum=maxWidth - sb.length();
        while(sum-->0){ sb.append(" ");}// 补上这一行最后的空格
        //fill_n(back_inserter(ans), maxWidth - ans.size(), ' ');
        return sb.toString();
    }
    //找到当前行最右边的单词下标
    public int findRight(String[] words,int maxWidth,int left){
        int right=left+1;
        int countWord=words[left].length();
        while(right<words.length&&(countWord+words[right].length()+1)<=maxWidth){
            countWord += words[right].length()+1;
            right++;
        }
        return right-1;
    }

求和与+1

二进制求和

67题

改题的整体思路是两个字符串不等,但是我们可以用0将其处理成相等的字符串比如11和01,然后就可以进行处理了

二进制运算的过程为,sum/2为进位,sum%2为当前位,sum每次取值的时候要加上上一轮的进位值,在最后的时候如果进位为1还需要增加一个1在前面

 public String addBinary(String a, String b) {
        StringBuilder ans = new StringBuilder();
        //carry 进位
        int ca = 0;
        for(int i = a.length() - 1, j = b.length() - 1;i >= 0 || j >= 0; i--, j--) {
            int sum = ca;
            //用0补齐较短的那个 sum+=当前字符或者0
            sum += i >= 0 ? a.charAt(i) - '0' : 0;
            sum += j >= 0 ? b.charAt(j) - '0' : 0;
            ans.append(sum % 2);//当前位
            ca = sum / 2;//进位
        }
        ans.append(ca == 1 ? ca : "");
        //得到的是反向的字符,因为我们是从最后一个开始处理的
        return ans.reverse().toString();
    }

加一

66题

 题目很简单,重点是如何用简洁的代码写出来,其实要考虑的情况也就是末尾为9的情况;对于非999的情况,我们直接进位,一旦取模于10不等于0就可以直接返回结果了,而等于999的情况,最终肯定是1000,所以只需要在0号位置附上1就行了

public int[] plusOne(int[] digits) {
        for (int i = digits.length - 1; i >= 0; i--) {
            digits[i]++;
            digits[i] = digits[i] % 10;
            if (digits[i] != 0) return digits;
        }
        digits = new int[digits.length + 1];
        digits[0] = 1;
        return digits;
    }

判断是否是有效数字

65题

 先筛选出不会出现的情况,e不能出现两次,使用一个idx变量记录字符串中e的位置,当idx等于-1的时候,说明整个有效数字中没有e的存在,对于没有e存在的情况和有e存在idx左边的情况其实是一样的,可以有.,对于有e存在右边的情况则是必须为数字不可以有.

check函数只在start处识别+和-;当有.的时候如果mustInteger或者有第二个.存在就直接return false;最后只要字符串是一连串数字就给他过;思路很简单重点在于理解题意

  public boolean isNumber(String s) {
        int n = s.length();
        char[] cs = s.toCharArray();
        int idx = -1;
        // e/E只能出现一次
        for (int i = 0; i < n; i++) {
            if (cs[i] == 'e' || cs[i] == 'E') {
                if (idx == -1) idx = i;
                else return false;
            }
        }
        boolean ans = true;
        if (idx != -1) {
            ans &= check(cs, 0, idx - 1, false);
            ans &= check(cs, idx + 1, n - 1, true);
        } else {
            ans &= check(cs, 0, n - 1, false);
        }
        return ans;
    }
    boolean check(char[] cs, int start, int end, boolean mustInteger) {
        if (start > end) return false;
        if (cs[start] == '+' || cs[start] == '-') start++;
        boolean hasDot = false, hasNum = false;
        for (int i = start; i <= end; i++) {
            if (cs[i] == '.') {
                if (mustInteger || hasDot) return false;
                hasDot = true;
            } else if (cs[i] >= '0' && cs[i] <= '9') {
                hasNum = true;
            } else {
                return false;
            }
        }
        return hasNum;
    }

路径与dp

路径

62题

一道很经典的动态规划题,机器人只能向下或者向右走,所以当前状态只能由上一个状态的上方和左方来,定义dp[i][j]为到达i,j位置的方法次数

 public int uniquePaths(int m, int n) {
        //dp[i][j]表示到到(i,j)有几种方法
        int[][] dp = new int[m][n];
        //base
        for (int i = 0; i <m ; i++) {
            dp[i][0]=1;
        }
        for (int i = 0; i <n ; i++) {
            dp[0][i]=1;
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j <n ; j++) {
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];

    }

有障碍的路径

63题

 需要对当前格子进行判断,如果当前格有障碍物那么到达它的机会==0,并且它如果在最左列或者最上行还会阻塞所有其他路径

public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        if (obstacleGrid == null || obstacleGrid.length == 0) {
            return 0;
        }

        // 定义 dp 数组并初始化第 1 行和第 1 列。
        int m = obstacleGrid.length, n = obstacleGrid[0].length;
        int[][] dp = new int[m][n];
        for (int i = 0; i < m ; i++) {
            if(obstacleGrid[i][0]==1)break;
            dp[i][0] = 1;
        }
        for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) {
            if(obstacleGrid[0][j]==1)break;
            dp[0][j] = 1;
        }

        // 根据状态转移方程 dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 进行递推。
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (obstacleGrid[i][j] == 0) {
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
                }
            }
        }
        return dp[m - 1][n - 1];
    }

螺旋矩阵

按照螺旋矩阵方式返回list

54题

 其算法核心在于使用四个变量分别表示上下左右边界,在抵达矩阵的边界后缩小其对应边界,例如在第一行遍历完之后应该缩小上边界,所以up应该++;其循环的退出条件为上下边界错开或者左右边界错开的时候

 List<Integer> res = new LinkedList<>();
        if (matrix.length == 0) {
            return res;
        }
        int up = 0, down = matrix.length - 1, left = 0, right = matrix[0].length - 1;
        while (true) {
            //输入第一排,第一排输入完毕后数字就没用了,直接舍弃掉
            for (int col = left; col <= right; col++) {
                res.add(matrix[up][col]);
            }
            //增加上边界
            if (++up > down) break;
            for (int row = up; row <= down; row++) {
                res.add(matrix[row][right]);
            }
            //减少右边界
            if (--right < left) break;
            for (int col = right; col >= left; col--) {
                res.add(matrix[down][col]);
            }
            //减少下边界
            if (--down < up) break;
            for (int row = down; row >= up; row--) {
                res.add(matrix[row][left]);
            }
            //增加左边界
            if (++left > right) break;
        }
        return res;

构建旋转矩阵

59题

 与上题的思路基本一致

 public int[][] generateMatrix(int n) {
        int up=0;
        int down=n-1;
        int left=0;
        int right=n-1;
        int[][] res = new int[n][n];
        int num=0;
        while (true){
            for (int i = left; i <= right; i++) {
                res[up][i]=++num;
            }
            //上边界抵达下边界
            if(++up>down)break;
            for (int i = up; i <=down ; i++) {
                res[i][right]=++num;
            }
            //右边界抵达左边界
            if(--right<left)break;
            for (int i = right; i >=left ; i--) {
                res[down][i]=++num;
            }
            //下边界抵达上边界
            if(--down<up)break;
            for (int i = down; i >=up ; i--) {
                res[i][left]=++num;
            }
            //左边界抵达右边界
            if(++left>right)break;
        }
        return res;
    }

插入区间 

  •  先一直添加区间:interval中的区间的右端点小于新区间的左端点,这是不重合的部分
  •  随后判断interval中区间的左端点小于等于新区间右端点的位置,这代表他们是重合的,一直合并成新的区间,并且随后添加区间
  • 最后当interval中的区间左端点大于新区间右端点的时候,继续添加集合,这也是不重合的部分

在ArrayList<int[]>转数组的时候使用如下方式写:res.toArray(new int[0][])可以提升程序运行速度

 public int[][] insert(int[][] intervals, int[] newInterval) {
        ArrayList<int[]> res = new ArrayList<>();
        int len = intervals.length;
        int i = 0;
        // 判断左边不重合
        while (i < len && intervals[i][1] < newInterval[0]) {
            res.add(intervals[i]);
            i++;
        }
        // 判断重合
        while (i < len && intervals[i][0] <= newInterval[1]) {
            newInterval[0] = Math.min(intervals[i][0], newInterval[0]);
            newInterval[1] = Math.max(intervals[i][1], newInterval[1]);
            i++;
        }
        res.add(newInterval);
        // 判断右边不重合
        while (i < len && intervals[i][0] > newInterval[1]) {
            res.add(intervals[i]);
            i++;
        }
        //list.toArray(new int[0][])这种写法确实有性能上的提升
        return  res.toArray(new int[0][]);
    }

计算n次幂

50题

 遇到关乎数字的题目要敏感边界问题,我们用long来取到n的值防止越界问题

此题的算法名为快速幂,其本质为分治算法,当N为奇数的时候多乘一个x即可,来看一个x的77次方的例子

x→x2→x4→+x9→+x19→x38→+x77一共经历了7次运算

public double myPow(double x, int n) {
        long N = n;//防止-n操作引起的整形越界
        return N >= 0 ? quickMul(x, N) : 1.0 / quickMul(x, -N);
    }
    //快速幂方法如果要计算x的16次方,x-》x2-》x4-》x8-》x16 只要计算四次就行了而不是把x乘以15次
    public double quickMul(double x, long N) {
        if (N == 0) {
            return 1.0;
        }
        double y = quickMul(x, N / 2);
        //如果除以2处不尽就多加一个x
        return N % 2 == 0 ? y * y : y * y * x;
    }

由于递归需要额外的栈空间,所以尝试用迭代来做,当遇到奇数的时候直接把值赋给结果(事实上递归也是这么做的),然后其他的就按照不断的累乘就行了

  public double myPow(double x, int n) {
        long N = n;
        return N >= 0 ? quickMul(x, N) : 1.0 / quickMul(x, -N);
    }

    public double quickMul(double x, long N) {
        double ans = 1.0;
        // 贡献的初始值为 x
        double x_contribute = x;
        // 在对 N 进行二进制拆分的同时计算答案
        while (N > 0) {
            if (N % 2 == 1) {
                // 如果 N 二进制表示的最低位为 1,那么需要计入贡献
                ans *= x_contribute;
            }
            // 将贡献不断地平方
            x_contribute *= x_contribute;
            // 舍弃 N 二进制表示的最低位,这样我们每次只要判断最低位即可
            N /= 2;
        }
        return ans;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值