剑指offer题目记录

剑指offer题目记录

仅作为个人笔记使用【每日一道题】

3.数组中的重复数字

题目描述:

在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。

Input:
{2, 3, 1, 0, 2, 5}

Output:
2
12345

解题思路

最优解题:要求时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。

对于这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素调整到第 i 个位置上进行求解。

以 (2, 3, 1, 0, 2, 5) 为例,遍历到位置 4 时,该位置上的数为 2,但是第 2 个位置上已经有一个 2 的值了,因此可以知道 2 重复

class Solution {
    public int findRepeatNumber(int[] nums) {
        int temp;
        for(int i=0;i<nums.length;i++){
            //这个while循环其实对数组进行了一次排序,让下标和值对应 
            while (nums[i]!=i){
                if(nums[i]==nums[nums[i]]){
                    return nums[i];
                }
                temp=nums[i];
                nums[i]=nums[temp];
                nums[temp]=temp;
            }
        }
        return -1;

    }
}
说明:
以(2,3,1,0,2,5)为例:
num[0] = 2 != 0 进入while循环 交换num[0]和num[2]的值:(1,3,2,0,2,5)
此时:num[0] = 1 依然不等于0 继续交换 (3,1,2,0,2,5)
num[0] = 3 继续交换 (0,1,2,3,2,5)
此时满足num[0] = 0	继续for循环
1,2,3都满足条件 当i=4的时候 
num[4] = 2 进入while循环 首先判断num[4]==num[num[4]]=2返回num[4]也就是2

第二种解题:使用HashSet:

思路:创建一个HashSet集合 遍历nums数组 先判断集合中是否有该元素 如果有直接返回该数字 如果没有加入集合中

class Solution {
    public int findRepeatNumber(int[] nums) {
       //使用HashSet解题
       //首先判断nums是否为空或者是否长度为0
       if(nums==null||nums.length==0){
           return -1;
       }
       //创建一个HashSet集合
       HashSet<Integer> hashset = new HashSet<>();
       for(int n:nums){
           if(hashset.contains(n)){
               return n;
           }
           else{
               hashset.add(n);
           }
       }
       return -1;
    }
}
           hashset.add(n);
       }
   }
   return -1;
}

}

4.二维数组中的查找

题目描述:
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

示例:

现有矩阵 matrix 如下:

[
 [1,   4,  7, 11, 15],
 [2,   5,  8, 12, 19],
 [3,   6,  9, 16, 22],
 [10, 13, 14, 17, 24],
 [18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。

给定 target = 20,返回 false

解题思路

该二维数组中的一个数,小于它的数一定在其左边,大于它的数一定在其下边。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来缩小查找区间,当前元素的查找区间为左下角的所有元素。

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        //如果二维数组为空或长度等于0 返回false
        if(matrix.length == 0 || matrix == null)
            return false;
        //分别获取行和列的长度
        int rows = matrix.length;
        int cols = matrix[0].length;
        //从右上角开始缩小范围 
        int r = 0, c = cols - 1; 
        while(r <= rows-1 && c >= 0) {
            if(target == matrix[r][c])
                return true;
                //如果这个数比matrix[r][c]大只有可能在这一行下面
            else if(target > matrix[r][c])
                r++;
                //如果这个数比matrix[r][c]小只有可能在这一列左边
            else
                c--;
        }
        return false;

    }
}

5.替换空格

题目描述:

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例 1:

输入:s = “We are happy.”
输出:“We%20are%20happy.”

解题思路:比较容易想到的办法:创建一个StringBuilder对象然后利用其append方法 先循环判断这个字符串是否有空格,

如果是空格.append("%20"),如果不是空格,.append(“s[i]”)

class Solution {
    public String replaceSpace(String s) {
      StringBuilder string = new StringBuilder();
        for (int i = 0; i < s.length(); i++){
            char c = s.charAt(i);
            if (c == ' '){
                string.append("%20");
            } else {
                string.append(c);
            }

        }
        return string.toString();

    }
}

第二种办法:由于每次替换会导致由一个字符变为三个字符,所以先创建一个长度为s的三倍长的数组,并且定义一个size来记录数组的实际长度 初始化size=0,遍历字符串,如果当前字符是空格 则array[size]=’%’ array[size+1]=‘2’ array[size+2]=‘0’,如果是普通字符array[size]=c,并且size++。

class Solution {
    public String replaceSpace(String s) {
        int length = s.length();
        char[] array = new char[length * 3];
        int size = 0;
        for (int i = 0; i < length; i++) {
            char c = s.charAt(i);
            if (c == ' ') {
                //先运算再加1 size[0]='%'
                array[size++] = '%';
                array[size++] = '2';
                array[size++] = '0';
            } else {
                array[size++] = c;
            }
        }
        String newStr = new String(array, 0, size);
        return newStr;
    }
}

从尾到头打印链表

题目描述

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例 1:

输入:head = [1,3,2]
输出:[2,3,1]

解题思路

当看到从尾到头打印 也就是逆序 后进先出想到栈,创建一个栈存放链表元素,当链表不为空时,依次将链表元素压入栈,然后创建以一个数组,
长度为栈的大小,使用循环将栈中元素依次放入数组即可。

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        Stack<ListNode> stack = new Stack<ListNode>();
        ListNode temp = head;
        while (head != null) {
           stack.push(head);
            head = head.next;
        }
        int size = stack.size();
       int[] print = new int[size];
        for (int i = 0; i < size; i++) {
            print[i] = stack.pop().val;
        }
        return print;
    }
}

10.1斐波那契数列

题目描述

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

解题思路

首先想到的是传统的递归算法,但是深度过大递归会导致栈溢出,我们可以将前2个数之和保存起来留在下次使用,使用动态规划

递归解决:【超时】
    class Solution {
public:
    int fib(int n) {
        if(n==0)
            return 0;
        else if(n==1)
            return 1; 
        return (fib(n-1)+fib(n-2))%1000000007;
    }
};

动态规划:
    class Solution {

 public int fib(int n) {
        int a = 0, b = 1, sum;
        for(int i = 0; i < n; i++){
            sum = (a + b) % 1000000007;
            a = b;
            b = sum;
        }
        return a;
 }

}

10.2青蛙跳台阶问题

题目描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:2
示例 2:

输入:n = 7
输出:21
示例 3:

输入:n = 0
输出:1

解题思路:本题和斐波那契数列本质相同,青蛙要跳n阶台阶,那么它最后一次只有两种可能,要么跳一阶要么跳两阶,

f(n)=f(n-1)+f(n-2),同斐波那契数列,唯一不同的是启始数字不同,

  1. 青蛙跳台阶问题: f(0)=1f(0)=1 , f(1)=1f(1)=1 , f(2)=2f(2)=2 ;
  2. 斐波那契数列问题: f(0)=0f(0)=0 , f(1)=1f(1)=1 , f(2)=1f(2)=1 。
动态规划:
    class Solution {
    public int numWays(int n) {
        int a =1; int b = 1;int sum;
        for(int i = 0;i < n;i++){
            sum = (a+b)%1000000007;
            a = b;
            b = sum;
        }
        return a;
    }
}

10.3变态青蛙跳台阶问题

题目描述

一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级… 它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

解题思路

我们可以分多种情况讨论:

  • 当跳一次的时候,一次跳1级或者2级的时候,都只有1种
  • 当n>2的时候,下次可能会跳1~n级之间,当下次跳1级的时候,还剩下f(n-1),跳2级的时候,剩下f(n-2),3级的时候,剩下f(n-3),
    类推到下次跳n-1级的时候,剩下f(n-(n-1))=f(1),当为n级时,剩下f(n-n)=f(0),
    所以总共有f(n)=f(n-1)+f(n-2)+…+f(1)+f(0)
  • f(n-1) = f(n-2)+…+f(1)+f(0)
  • 所以:f(n) = 2f(n-1) 所以f(n)是一个等比数列
  • 因为f(1) = 1 f(2) = 2
  • f(n) = pow(2, n - 1)
public int JumpFloorII(int target) {
    return (int) Math.pow(2, target - 1);
}

11.旋转数组的最小数字

题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

示例 1:

输入:[3,4,5,1,2]
输出:1
示例 2:

输入:[2,2,2,0,1]
输出:0

解题思路

  1. 最容易想到的就是直接排序后数组的第一个元素肯定是最小的元素 但时间复杂度高
  2. 一看到排序数组的查找 应该考虑使用二分法
排序解决:
    class Solution {
    public int minArray(int[] numbers) {
     Arrays.sort(numbers);
     return numbers[0];
    }
}
二分法:
    class Solution {
    public int minArray(int[] numbers) {
         int left = 0, right = numbers.length - 1;
    while (left < right) {
        //找出left和right中间值的索引
        int mid = left + (right - left) / 2;
        if (numbers[mid] > numbers[right]) {
            //如果中间值大于最右边的值,说明旋转之后最小的
            //数字肯定在mid的右边,比如[3, 4, 5, 6, 7, 1, 2]
            left = mid + 1;
        } else if (numbers[mid] < numbers[right]) {
            //如果中间值小于最右边的值,说明旋转之后最小的
            //数字肯定在mid的前面,比如[6, 7, 1, 2, 3, 4, 5],
            //注意这里mid是不能减1的,比如[3,1,3],我们这里只是
            //证明了numbers[mid]比numbers[right]小,但有可能
            //numbers[mid]是最小的,所以我们不能把它给排除掉
            right = mid;
        } else {
            //如果中间值等于最后一个元素的值,我们是没法确定最小值是
            // 在mid的前面还是后面,但我们可以缩小查找范围,让right
            // 减1,因为即使right指向的是最小值,但因为他的值和mid
            // 指向的一样,我们这里并没有排除mid,所以结果是不会有影响的。
            //比如[3,1,3,3,3,3,3]和[3,3,3,3,3,1,3],中间的值
            //等于最右边的值,但我们没法确定最小值是在左边还是右边
            right--;
        }
    }
    return numbers[left];
    }
}

14剪绳子

【这个结论比较重要 要牢记 复习重点】
题目描述

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

解题思路:

数论:【记忆

1. 任何大于1的数都可由2和3相加组成(根据奇偶证明)
2. 因为2*2=1*4,2*3>1*5, 所以将数字拆成2和3,能得到的积最大
3. 因为2*2*2<3*3, 所以3越多积越大 

解题:

当n<=3时 按照理论应该3就是最优解 但是题目要求必须至少剪一段 所以2+1
当n>3时候 n/3=3x+b n%3可能有三种情况
1. n%3=0  直接返回3^x即可
2. n%3=1  此时注意  因为2*2>3*1 所以应该把其中一个3拆分出来 变成2*2
3. n%3=2  返回2*3^x即可

代码

class Solution {
    public int cuttingRope(int n) {
        if(n <= 3) return n - 1;
        int a = n / 3, b = n % 3;
        if(b == 0) return (int)Math.pow(3, a);
        if(b == 1) return (int)Math.pow(3, a - 1) * 4;
        return (int)Math.pow(3, a) * 2;
    }
}

2021/8/10 力扣204 计数质数

解决方法:埃氏筛:

boolean[] arr = new boolean[n];
//把arr数组全部设置为true
Arrays.fill(arr,true);
for(int i = 2;ii<n;i++){
if(arr[i]){
for(int j =i
i;j<n;j+=i){
arr[j] = false;
}
}
}
//遍历
int count = 0;//记录质数个数
for(int i= 2;i<n;i++){
if(arr[i]){
count++;
}
}
return count;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值