剑指offer---持续更新中...

目录

1.数组中重复的数字

2.二维数组中的查找

3.替换字符串中的空格

4.从尾到头打印链表

5.斐波那契数列

6.旋转数组的最小数字

7.二进制中‘1’的个数

8.打印从1到最大的n位数

9.删除链表的节点

10.链表中倒数最后k个结点


1.数组中重复的数字

描述:

 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是2或者3。存在不合法的输入的话输出-1

数据范围:0\le n \le 10000 \0≤n≤10000

进阶:时间复杂度O(n)\O(n) ,空间复杂度O(n)\O(n)

做题思路:重头到尾扫描数组S中的每一个元素,当扫描到第i个元素的时候,比较第i个元素位置的值m是否等于i,如果相等,则说明该元素已经在排好序的位置,继续扫描其他元素;如果不相等,先判断m是否等于S[m],相等则说明不同位置上的元素值相等,即元素重复。直接返回元素;否则交换m和S[m]将他们放置到排好序的位置。

图示

 代码

public void swap(int[] numbers,int a,int b){
        int t=numbers[a];
        numbers[a]=numbers[b];
        numbers[b]=t;     
    }
         
         
         
    public int duplicate (int[] numbers) {
       int i=0,n=numbers.length;
        while(i<n){
            if(numbers[i]==i){
                i++;
                continue;
            }else {
                if(numbers[i]==numbers[numbers[i]]){
                    return numbers[i];
                }
                else
                    swap(numbers,i,numbers[i]);
            }
                      
        }
        return -1;
    }

2.二维数组中的查找

描述:
在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

[

[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]

]

给定 target = 7,返回 true。

给定 target = 3,返回 false。

数据范围:矩阵的长宽满足 0 \le n,m \le 5000≤n,m≤500 , 矩阵中的值满足 0 \le val \le 10^90≤val≤109
进阶:空间复杂度 O(1)O(1) ,时间复杂度 O(n+m)O(n+m)

做题思路:首先看四个角,左上与右下必定为最小值与最大值,而左下与右上就有规律了:左下元素大于它上方的元素,小于它右方的元素,右上元素与之相反。既然左下角元素有这么一种规律,相当于将要查找的部分分成了一个大区间和小区间,每次与左下角元素比较,我们就知道目标值应该在哪部分中。

具体做法

1.首先获取矩阵的两个边长,判断特殊情况。

2.首先以左下角为起点,若是它小于目标元素,则往右移动去找大的,若是他大于目标元素,则往上移动去找小的。

3.若是移动到了矩阵边界也没找到,说明矩阵中不存在目标值。

代码:

public class Solution {
    public boolean Find(int target, int [][] array) {
        if(array.length==0||array[0].length==0)
            return false;
        int n=array.length;
        int m=array[0].length;
        for(int i=n-1,j=0;i>=0&&j<m;){
            if(array[i][j]>target){
                i--;               
            }else if(array[i][j]<target){
                j++;
            }else
                return true;        
        } 
        return false;
    }  
}

3.替换字符串中的空格

描述:
请实现一个函数,将一个字符串s中的每个空格替换成“%20”。

例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

数据范围:0 \le len(s) \le 1000 \0≤len(s)≤1000 。保证字符串中的字符为大写英文字母、小写英文字母和空格中的一种。

做题思路

①创建一个长度为3*n的数组,对字符串中的每个字符进行遍历,进行判断,如果碰到字符是空格,我们就将空格那个i位置变成%,2与0分别占到i+1与i+2。如果不是空格,就将字符按照数组的位置依次存储下去。直至将字符串完全遍历结束。最后将数组输出得到的就是答案。

代码
 

public String replaceSpace(String s) {
    int length = s.length();
    char[] array = new char[length * 3];
    int index = 0;
    for (int i = 0; i < length; i++) {
        char c = s.charAt(i);
        if (c == ' ') {
            array[index++] = '%';
            array[index++] = '2';
            array[index++] = '0';
        } else {
            array[index++] = c;
        }
    }
    String newStr = new String(array, 0, index);
    return newStr;
}

4.从尾到头打印链表

描述
输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。

如输入{1,2,3}的链表如下图:

返回一个数组为[3,2,1]

0 <= 链表长度 <= 10000

解题思路:

①非递归
listNode 是链表,只能从头遍历到尾,但是输出却要求从尾到头,这是典型的"先进后出",我们可以想到栈!
        ArrayList 中有个方法是 add(index,value),可以指定 index 位置插入 value 值,所以我们在遍历 listNode 的同时将每个遇到的值插入到 list 的 0 位置,最后输出 list 即可得到逆序链表。
代码:

public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        ListNode tmp=listNode;
        while(tmp!=null){
            list.add(0,tmp.val);
            tmp=tmp.next;
        }
        return list;    
    }
}

②递归

代码:

public class Solution {
    ArrayList<Integer> list=new ArrayList();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        if(listNode!=null){
            printListFromTailToHead(listNode.next);
            list.add(listNode.val);
        }       
        return list;
    }
}

5.斐波那契数列

描述:
大家都知道斐波那契数列,现在要求输入一个正整数 n ,请你输出斐波那契数列的第 n 项。

斐波那契数列是一个满足 fib(x)=

的数列

数据范围:1\leq n\leq 401≤n≤40

要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n) ,本题也有时间复杂度 O(logn)O(logn) 的解法

解题思路

①非递归
根据题目可知,第一项与第二项都是1,所以首先判断n的值,当n=1或n=2时,直接返回1,当n>3时,定义一个变量sum,用于存储前两项的和,求完一次,将a与b的值变成b与sum的值,重复此过程,直至求至n项,将sum返回。

代码:

public class Solution {
    public int Fibonacci(int n) {
        if(n==1||n==2){
            return 1;
        }else{
            int sum=0,a=1,b=1;
           for(int i=3;i<=n;i++){
               sum=a+b;
               a=b;
               b=sum;
           }
            return sum;
        }
    }
}

②递归
我们可以根据公式倒推,因为F(n)=F(n−1)+F(n−2),而F(n−1)与F(n−2)又可以作为子问题继续计算,因此可以使用递归。

终止条件: 当递归到数列第1项或是第0项的时候,可以直接返回数字。
返回值: 返回这一级子问题的数列值。
本级任务: 获取本级数列值:即前两项相加。
具体做法:

step 1:低于2项的数列,直接返回n。
step 2:对于当前n,递归调用函数计算两个子问题相加。
代码:

public class Solution {
    public int Fibonacci(int n) {
        //从0开始,第0项是0,第一项是1
        if (n <= 1)   
             return n;
        else{
            //根据公式递归调用函数
            return Fibonacci(n - 1) + Fibonacci(n - 2);
        }
    }
}

6.旋转数组的最小数字

描述:
有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。

数据范围:1 ≤ n ≤ 10000,数组中任意元素的值:0 ≤ val ≤ 10000

要求:空间复杂度:O(1) ,时间复杂度:O(logn)

解题思路:

①二分法
旋转数组将原本有序的数组分成了两部分有序的数组,因为在原始有序数组中,最小的元素一定是在首位,旋转后无序的点就是最小的数字。我们可以将旋转前的前半段命名为A,旋转后的前半段命名为B,旋转数组即将AB变成了BA,A部分和B部分都是各自有序的,每次比较中间值,确认目标值(最小元素)所在的区间。

1:双指针指向旋转后数组的首尾,作为区间端点。

2:若是区间中点值大于区间右界值,则最小的数字一定在中点右边。

3:若是区间中点值等于区间右界值,则是不容易分辨最小数字在哪半个区间,比如[1,1,1,0,1],应该逐个缩减右界。

4:若是区间中点值小于区间右界值,则最小的数字一定在中点左边。

5:通过调整区间最后即可锁定最小值所在。

代码:

public class Solution {
    public int minNumberInRotateArray(int [] array) {
        int left=0;
        int right=array.length-1;
        while(left<right){
            int mid=(left+right)/2;
            if(array[mid]>array[right]){
                left=mid+1;
            }else if(array[mid]<array[right]){
                right=mid;
            }else{
                right--;
            }          
          } 
        return array[left]; 
    }
}

②遍历数组 

设置一个变量res为array[0],从左到右遍历整个数组,依次检查当前元素与记录元素的大小,若是当前元素更小,则记录最小的元素更新。遍历结束后,记录的元素则为数组的最小数字。

代码:

public class Solution {
    public int minNumberInRotateArray(int [] array) {
        int res = array[0];
        for(int i = 1; i < array.length; i++)
            res = Math.min(res, array[i]);
        return res;
    }
}

7.二进制中‘1’的个数

描述:
输入一个整数 n ,输出该数32位二进制表示中1的个数。其中负数用补码表示。

数据范围:- 2^{31} <= n <= 2^{31}-1−231<=n<=231−1

即范围为:-2147483648<= n <= 2147483647−2147483648<=n<=2147483647

本题知识点:位运算
1.按位与运算符( & )
        运算规则:只有两个数的二进制同时为1,结果才为1,否则为0。(负数按补码形式参加按位与运算)

举例:3 &5 即 00000011 & 00000101 = 00000001 ,所以 3 & 5的值为1。

2.按位或运算符( | )
        运算规则:参加运算的两个数只要两个数中的一个为1,结果就为1。

        即  0 | 0= 0 ,  1 | 0= 1  , 0 | 1= 1  ,  1 | 1= 1 。

举例:2 | 4 即 00000010 | 00000100 = 00000110 ,所以2 | 4的值为 6 

3.异或运算符( ^
        运算规则:参加运算的两个数,如果两个相应位为“异”(值不同),则该位结果为1,否则为0。

        即 0 ^ 0=0  , 0 ^ 1= 1  , 1 ^ 0= 1  , 1 ^ 1= 0 。

举例: 2 ^ 4 即 00000010 ^ 00000100 =00000110 ,所以 2 ^ 4 的值为6 。

4.<<运算符( << )
        运算规则:n << i = n * 2^i 。(箭头指向左边 ‘乘’ )

举例:18 << 2 = 18 * 2^2 =72,18 << 3 = 18 * 2^3 = 144  

5.>>运算符( >> )
        运算规则:n << i = n / 2^i 。(箭头指向右边 ‘除’ ,除不尽有余数向下取整)

举例:18 >> 2 = 18 / 2^2 = 4,18 >> 3 = 18 / 2^3 = 2 

解题思路:

        移位运算,每次移动一位就可以。至于怎么统计到1呢?我们都只知道数字1与数字相位与运算,其实只是最后一位为1就是1,最后一位为0就是0,这样我们只需要将数字1移位运算,就可以遍历二进制的每一位,再去做位与运算,结果为1的就是二进制中为1的。

1:遍历二进制的32位,通过移位0-31次实现。 

2:将移位后的1与数字进行位与运算,结果为1就记录一次。

代码:

public class Solution {
    public int NumberOf1(int n) {
        int count=0;
        for(int i=0;i<32;i++){
            if((n&(1 << i))!=0){
                count++;
            }
        }
        return count;
    }
}

8.打印从1到最大的n位数

描述
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

1. 用返回一个整数列表来代替打印
2. n 为正整数,0 < n <= 5

解题思路:首先我们需要算出n位数的最大值,也就是10^n-1,根据这个,我们只需要从1遍历到10^n-1,创建一个长度为10^n-1的数组,将其一一存入后,将数组返回即可。

代码:
 

public class Solution {
    public int[] printNumbers (int n) {
        int sum=1;
        for(int i=0;i<n;i++){
            sum*=10;
        }
        int[] arr=new int[sum-1];
        for(int i=1;i<sum;i++){
            arr[i-1]=i;
        }
        return arr;
    }
}

9.删除链表的节点

描述:
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

1.此题对比原题有改动

2.题目保证链表中节点的值互不相同

3.该题只会输出返回的链表和结果做对比,所以若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点

数据范围:

0<=链表节点值<=10000

0<=链表长度<=10000

思路分析:

1.首先判断该单链表是否为空,如果为空直接返回null

2.当链表不为空时,那我们就从头结点开始遍历单链表

3.因为头节点不能动,所以我定义一个辅助指针指向头节点,用于遍历单链表

4.从头节点开始遍历单链表

        4.1根据temp.next.val=val,找到对应节点,temp.next=temp.next.next

        4.2直到链表最后,仍然没有找到对应节点,返回null

示意图:


 代码:

 

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param head ListNode类 
     * @param val int整型 
     * @return ListNode类
     */
    public ListNode deleteNode (ListNode head, int val) {
        if(head == null){
            return null;
        }
        ListNode temp=head;
        if(head.val==val){
            head=head.next;
            return head;
        }
        while(temp.next!=null){
            if(temp.next.val == val){
                temp.next=temp.next.next;
                break;
            }
            temp=temp.next;
        }
        return head; 
    }
   
}

10.链表中倒数最后k个结点

描述:
输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。

如果该链表长度小于k,请返回一个长度为 0 的链表。

要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)

进阶:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

数据范围:0 <= n <= 10^5,0 <= ai<= 10^9,0 <= k<= 10^9

例如输入{1,2,3,4,5},2时,对应的链表结构如下图所示:

 

其中蓝色部分为该链表的最后2个结点,所以返回倒数第2个结点(也即结点值为4的结点)即可,系统会打印后面所有的节点来比较。

思路分析:
1.首先我们需要知道该单链表的长度,定义一个辅助变量temp指向head,从头到尾遍历链表,temp每往后挪动一次,length++,直到最后temp指向最后一个节点,得到链表的长度length

2.由于单链表只能从头到尾遍历,所以我们需要计算temp从头节点到倒数第k个节点需要挪动length-k次,返回此时的temp,即是k节点后的所有节点

代码:

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pHead ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    public ListNode FindKthToTail (ListNode pHead, int k) {
        int length=1;
        if(pHead == null||k == 0){
            return null;
        }
        ListNode temp=pHead;  
        while(temp.next != null){
            temp=temp.next;
            length++;
        }
        if(k > length){
            return null;
        }
        temp=pHead; 
        int num=length-k;//5-2=3
        for(int i=1 ;i < num; i++){//1 2 
            temp=temp.next;             
        }
        if(k == length){
            return temp;
        }
        return temp.next;
        
    }
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小杨不会Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值