牛客剑指Offer题解综合

@剑指offer题解整理

牛客剑指Offer题解综合

6题、旋转数组中的最小数字

旋转之后的数组实际上可以划分成两个有序的子数组:前面子数组的大小都大于后面子数组中的元素。注意到实际上最小的元素就是两个子数组的分界线。本题目给出的数组一定程度上是排序的,因此我们试着用二分查找法寻找这个最小的元素。

  • 思路
    • 用两个指针left,right分别指向数组的第一个元素和最后一个元素。按照题目的旋转的规则,第一个元素应该是大于最后一个元素的(没有重复的元素)。但是如果不是旋转,第一个元素肯定小于最后一个元素。
    • 找到数组的中间元素。
      中间元素大于第一个元素,则中间元素位于前面的递增子数组,此时最小元素位于中间元素的后面。我们可以让第一个指针left指向中间元素。移动之后,第一个指针仍然位于前面的递增数组中。
      中间元素小于第一个元素,则中间元素位于后面的递增子数组,此时最小元素位于中间元素的前面。我们可以让第二个指针right指向中间元素。移动之后,第二个指针仍然位于后面的递增数组中。
    • 按照以上思路,第一个指针left总是指向前面递增数组的元素,第二个指针right总是指向后面递增的数组元素。最终第一个指针将指向前面数组的最后一个元素,第二个指针指向后面数组中的第一个元素。
      也就是说他们将指向两个相邻的元素,而第二个指针指向的刚好是最小的元素,这就是循环的结束条件。到目前为止以上思路很耗的解决了没有重复数字的情况。有重复数字怎不能用这种方法。例如:{1,0,1,1,1} 和 {1,1, 1,0,1} 都可以看成是递增排序数组{0,1,1,1,1}的旋转。
  • 本题的代码:
import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array.length == 0 && array == null){
            return -1;
        }
        int frist = 0;
        int last = array.length - 1;
        int mid ;
        while(last != frist+1){
            mid = (last + frist)/2;
            if(array[mid] >= array[frist]){
                frist = mid;
            }else if (array[mid] <= array[last]){
                last = mid;
            }
        }
        return array[last];
    }
}

如果原数组是一个非减数组

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array.length == 0){
            return 0;
        }
        int index1 = 0;
        int index2 = array.length-1;
        if(array[index1]<array[index2]){
                return array[index1];
            }
        int mid = (index2+index1)/2;
        if(array[index1]==array[index2]||array[index1]==array[mid]||array[mid]==array[index2]){
            for(int i = 0;i<array.length-1;i++){
                if(array[i]>array[i+1]){
                    return array[i+1];
                }else{
                    if(i==array.length-2){
                        if(array[i]<array[array.length-1]){
                            return array[0];
                        }
                    }
                }
            }
        }
         
        while(true){
            mid = (index1+index2)/2;
            if(array[mid]<array[index2]){
                index2=mid;
            }else{
                index1=mid+1;
            }
            if(index1==index2){
                return array[index1];
            }
        }
         
    }
}

13题、调整数组顺序使奇数位于偶数前面

在这里插入图片描述
题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

  • 我的代码
    • 思路:
      建立一个新的数组,先遍历一次将奇数依次存放到数组中去,然后再遍历一次将偶数依次存放到数组中去。用空间换时间。时间复杂度 O(N),空间复杂度O(N)。
public class Solution {
    public void reOrderArray(int[] array) {
        int[] arr = new int[array.length];
        int i=0,k=0;
        while(i<array.length){
            if(array[i]%2 == 1 ){
                arr[k++] = array[i];
            }
            i++;
        }
        i = 0;
        while(i<array.length){
            if(array[i]%2 == 0){
                arr[k++] = array[i];
            }
            array[i] = arr[i];
            i++;
        }
    }
}
  • 网上各路神仙的思路及代码
    • 从题目得出的信息:
      相对位置不变—>保持稳定性奇数位于前面,偶数位于后面 —>存在判断,挪动元素位置
      这些都和内部排序算法相似,考虑到具有稳定性的排序算法不多,例如插入排序,归并排序等;这里采用插入排序的思想实现。
public class Solution {
    public void reOrderArray(int [] array) {
        //相对位置不变,稳定性
        //插入排序的思想
        int m = array.length;
        int k = 0;//记录已经摆好位置的奇数的个数
        for (int i = 0; i < m; i++) {
            if (array[i] % 2 == 1) {
                int j = i;
                while (j > k) {//j >= k+1
                    int tmp = array[j];
                    array[j] = array[j-1];
                    array[j-1] = tmp;
                    j--;
                }
                k++;
            }
        }
    }
}
  • 类似冒泡算法,前偶后奇数就交换
class Solution {
public:
    void reOrderArray(vector<int> &array) {
        for (int i = 0; i < array.size();i++){
            for (int j = array.size() - 1; j>i;j--){
                if (array[j] % 2 == 1 && array[j - 1]%2 == 0) { //前偶后奇交换
                    swap(array[j], array[j-1]);
                }
            }
        }
    }
};

14、链表中倒数第K个结点

牛客剑指Offer题解综合_第1张图片

  • 思路:
    建立一个栈,将链表中所有的结点全部push到栈中,需要倒数第k个结点,就从栈中弹出k个。代码如下。
import java.util.Stack;

/*
思路:建立一个栈,将链表中所有的结点全部push到栈中,需要倒数第k个结点,就从栈中弹出k个。
 */
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head == null || k<=0){
            return null;
        }
        Stack<ListNode> stack = new Stack();
        ListNode crr = head;
        int size = 0;//记录链表长度
        while(true){
            if(crr == null){
                break;
            }
            stack.push(crr);
            size++;
            crr = crr.next;
        }
        ListNode kTh = null;
        if(k <= size){//判断链表长度与K的关系,防止越界
            for(int i = 1;i <= k;i++){
                if(i==k){
                    kTh = stack.peek();
                }
                stack.pop();
            }
        }

        return kTh;
    }
  • 大神的思路及代码
    最佳代码:Java代码,通过校验。代码思路如下:两个指针,先让第一个指针和第二个指针都指向头结点,让第一个指真走(k-1)步,到达第k个节点。然后两个指针同时往后移动,当第一个结点到达末尾的时候,第二个结点所在位置就是倒数第k个节点了。
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head==null||k<=0){
            return null;
        }
        ListNode pre=head;
        ListNode last=head;       
        for(int i=1;i<k;i++){
            if(pre.next!=null){
                pre=pre.next;
            }else{
                return null;
            }
        }
        while(pre.next!=null){
            pre = pre.next;
            last=last.next;
        }
        return last;
    }
}

还有一个简洁版的:思想是一样的。

public ListNode FindKthToTail12(ListNode head, int k) {
	if(head==null||k<=0){
            return null;
        }
  	ListNode l = head;
 	 while(head != null){
    		k--;
     		if(k < 0)  
     			l = l.next;
  		 	head = head.next;
  	}
 	 return k <= 0 ? l : null;
}

15、反转链表

牛客剑指Offer题解综合_第2张图片
反转链表,切记,注意原链表的头结点的next是不是null。

  • 详解
public class Solution {
    public ListNode ReverseList(ListNode head) {
       
        if(head==null)
            return null;
        //head为当前节点,如果当前节点为空的话,那就什么也不做,直接返回null;
        ListNode pre = null;
        ListNode next = null;
        //当前节点是head,pre为当前节点的前一节点,next为当前节点的下一节点
        //需要pre和next的目的是让当前节点从pre->head->next1->next2变成pre<-head next1->next2
        //即pre让节点可以反转所指方向,但反转之后如果不用next节点保存next1节点的话,此单链表就此断开了
        //所以需要用到pre和next两个节点
        //1->2->3->4->5
        //1<-2<-3 4->5
        while(head!=null){
            //做循环,如果当前节点不为空的话,始终执行此循环,此循环的目的就是让当前节点从指向next到指向pre
            //如此就可以做到反转链表的效果
            //先用next保存head的下一个节点的信息,保证单链表不会因为失去head节点的原next节点而就此断裂
            next = head.next;
            //保存完next,就可以让head从指向next变成指向pre了,代码如下
            head.next = pre;
            //head指向pre后,就继续依次反转下一个节点
            //让pre,head,next依次向后移动一个节点,继续下一次的指针反转
            pre = head;
            head = next;
        }
        //如果head为null的时候,pre就为最后一个节点了,但是链表已经反转完毕,pre就是反转后链表的第一个节点
        //直接输出pre就是我们想要得到的反转后的链表
        return pre;
    }
}
  • 我的几个失败的做法
    • 第一个
    • 想用三个结点来反转。但我初始化的时候就做错了,因为我所用的三个结点pre,crr,next。分别指的是头节点,head.next,head.next.next。从代码里看到,当反转后的尾结点的next并不是null。
public ListNode ReverseList(ListNode head) {
    if(head == null){
        return null;
    }
    ListNode pre = head;
    ListNode crr = head.next;
    ListNode next = crr.next;
    while(pre.next != null){
        if (next ==null){
            crr.next = pre;
            pre = crr;
            break;
        }
        crr.next = pre;
        pre = next.next;
        next.next = crr;
        crr = pre.next;
        pre.next = next;
        next = crr.next;
        if (crr.next == null){
            crr.next = pre;
            pre = crr;
            break;
        }
        if (next.next == null){
            break;
        }
    }
    return pre;
}
  • 第二个方法
  • 利用一个栈,将所有的链表结点全部压入栈中,然后再依次弹出。最后要注意,将原来的头结点的next 指向null。
public ListNode ReverseList(ListNode head) {
        if(head == null){
            return null;
        }
        ListNode crr = head;
        Stack<ListNode> stack = new Stack();
        while(crr.next != null) {
                stack.push(crr);
                crr = crr.next;
        }
        ListNode reHead = crr;
        while(!stack.empty()){
            crr.next = stack.peek();
            crr = crr.next;
            stack.pop();
        }
        //将原链表头结点指向null
        crr.next= null;
        return reHead;
    }

16、合并两个排序的链表牛客剑指Offer题解综合_第3张图片

  • 思路
    先定义两个指针,一个代表的是合并后链表的新的头结点,另一个是去遍历这两个链表的结点。通过遍历两个链表,比较当前结点next与另一个链表的结点的val大小。然后决定当前结点该指向哪个结点。代码如下:两种解决方案。
//非递归方法
/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1 == null && list2 == null){
            return null;
        }else if (list1 == null && list2 != null){
            return list2;
        }else if (list1 != null && list2 == null){
            return list1;
        }else{
                //定义两个指针newHead用来确定合并后的头结点,crr通过他去合并两个链表
        ListNode newHead = null;
        ListNode crr = null;
        while(list1 != null && list2 != null){
            if(list1.val >= list2.val){
                if(crr == null){
                    crr = list2;
                    newHead = crr;
                }else{
                    crr.next = list2;
                    crr = crr.next;
                }
                list2 = list2.next;
            }else{
                if(crr == null){
                    crr = list1;
                    newHead = crr;
                }else{
                    crr.next = list1;
                    crr = crr.next;
                }
                list1 = list1.next;
            }
        }
        //因为两个链表都是递增的,所以遍历完任何一个,直接连接剩余部分。
        if(list1 == null){
             crr.next = list2;
        }
        if(list2 == null){
             crr.next = list1;
        }
          return newHead;
        }
       
    }
}
  • 另外,还有一种递归的方法。代码简洁易懂。
public ListNode Merge(ListNode list1,ListNode list2) {
       if(list1 == null){
           return list2;
       }
       if(list2 == null){
           return list1;
       }
       if(list1.val <= list2.val){
           list1.next = Merge(list1.next, list2);
           return list1;
       }else{
           list2.next = Merge(list1, list2.next);
           return list2;
       }       
   }

65、 矩阵中的路径(回溯)

  • 基本思想
    • 根据给定数组,初始化一个标志位数组,初始化为false,表示未走过,true表示已经走过,不能走第二次。
    • 根据行数和列数,遍历数组,先找到一个与str字符串的第一个元素相匹配的矩阵元素,进入hasPathCore
    • 因为给定的matrix是一个一维数组,用二维坐标表示一维数组的形式是:row*cols+col;
    • 确定递归终止条件:1.越界 2.当前找到的矩阵值不等于数组对应位置的值 3.已经走过。这三类情况,都直接false
    • 成功的条件是strpath = = str.length,就是待判定的字符串str的索引已经找到了符合的最后一位,此时说明是匹配成功的。
    • 本题的精髓在于,通过递归,不断地寻找周围四个格子是否符合条件,只要有一个格子符合条件,就继续再找这个符合条件的格子的四周是否存在符合条件的格子,直到满足条件终止递归或者不满足条件strpath–,将走过的路径变为false,完成回溯过程。
  • 代码:
public class Solution {
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str){
        if(matrix == null || rows<1 || cols<1 || str == null){
            return false;
        }
        int strpath = 0;//已成功匹配字符个数
        boolean[] path = new boolean[rows*cols];//布尔类型标志矩阵,初始化为false
        //以二位数组遍历的方式去遍历数组,找到第一个符合位置的点。
        for(int row = 0; row < rows;row++){
            for(int col = 0; col< cols;col++){
                if(hasPathCore(matrix,rows,cols,row,col,str,path,strpath)){
                    return true;
                }
            }
        }
        return false;
    }
     /**
     * hasPathCore方法用来判断当前点是否能匹配str中的字符,如果能,递归其周围四个点。若不能弹回。
     * @param matrix  初始矩阵
     * @param rows    初始矩阵的行数
     * @param cols    初始矩阵的列数
     * @param row      当前行
     * @param col       当前列
     * @param str       目标矩阵
     * @param path      标志矩阵
     * @param strpath   成功匹配的个数
     * @return hasPath  能否匹配当前字符
     */
    private boolean hasPathCore(char[] matrix, int rows, int cols,int row,int col, char[] str,boolean[] path,int strpath){
        if(strpath == str.length){
            return true;
        }
        boolean hasPath =false;
        if(row >= 0 && row<rows && col >= 0 && col < cols && matrix[row*cols+col] == str[strpath] && !path[row*cols+col]){
            strpath++;
            path[row*cols+col] = true;
            hasPath = hasPathCore(matrix,rows,cols,row+1,col,str,path,strpath) 
                    || hasPathCore(matrix,rows,cols,row,col+1,str,path,strpath) 
                    ||hasPathCore(matrix,rows,cols,row-1,col,str,path,strpath)
                    ||hasPathCore(matrix,rows,cols,row,col-1,str,path,strpath);
                //回溯过程:
                if(!hasPath){
            			strpath--;
            			path[row*cols+col] = false;
        		}
        }
        return hasPath;
    }

}

66、 机器人的运动范围(回溯)

  • 核心思路:
    1.从(0,0)开始走,每成功走一步标记当前位置为true,然后从当前位置往四个方向探索,
    返回1 + 4 个方向的探索值之和。
    2.探索时,判断当前节点是否可达的标准为:
    1)当前节点在矩阵内;
    2)当前节点未被访问过;
    3)当前节点满足limit限制。-
  • 代码
public class Solution{
public int movingCount(int threshold, int rows, int cols)
    {
        if( rows < 0 || cols < 0){
            return 0;
        }
        //为了避免出现重复,应该添加一个同样大小的标识型矩阵
        boolean[][] flag = new boolean[rows][cols];
        //定义一个记录机器人能够走多少个格子的变量;
        int count = 0;
        count = backMovingCount(threshold, rows, cols, 0, 0, count, flag);
        return count;
    }
    /**
     *
     * @param threshold 阈值
     * @param rows 矩阵行数
     * @param cols 矩阵列数
     * @param i    当前行数
     * @param j    当前列数
     * @param count   已走多少个格子
     * @param flag 标识矩阵
     * @return    当前走的格子数
     */
    private int backMovingCount(int threshold, int rows, int cols,int i,int j,int count, boolean[][] flag){
        if(i>=0 && i<rows && j>=0 && j<cols && indexSum(i)+indexSum(j) <= threshold && !flag[i][j]){
        	//用count来记录机器人走过的格子数,在递归的过程中要一直更新count值。
            count++;
            flag[i][j] = true;
            count = backMovingCount( threshold, rows, cols, i+1, j, count, flag);
            count = backMovingCount( threshold, rows, cols, i-1, j, count, flag);
            count = backMovingCount( threshold, rows, cols, i, j+1, count, flag);
            count = backMovingCount( threshold, rows, cols, i, j-1, count, flag);
        }
        return count;
    }
    private int indexSum(int i){
        int sum = 0;
        while(i/10 != 0 || i%10 != 0){
            sum += (i%10);
            i /= 10;
        }
        return sum;
    }
}

你可能感兴趣的:(代码)