剑指 Offer 刷题总结

03. 数组中重复的数字

题目描述:

找出数组中重复的数字。

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

示例:

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 

思路:认真看题目描述!!!

我的思路(菜逼思路):
也是最简单的思路,直接排序,然后查找,找到两个相邻元素相同,则为重复元素,直接返回即可(时间复杂度为 O(nlogn),空间复杂度为 O(n))

进阶思路:
通过看题目描述可知,数组长度为 n,且所有数字都在 0~n-1 的范围内,所以,我们可以每次判断索引为 i 的元素是否为 i,如果不是(假定该索引元素为 n),则判断该元素索引为 n 的元素是否相等,如果相等,则为重复元素,直接返回;如果不相等,则将该元素与索引为 n 的元素交换,直到换到索引为 i 的元素是 i 为止(时间复杂度为 O(n),空间复杂度为 O(1))
如:
	[2, 3, 1, 0, 2, 5, 3]
	索引 0 的元素为 2
	则将其索引为 2 的元素交换
	此时:[1, 3, 2, 0, 2, 5, 3]
	索引 0 的元素为 1 
	将其与索引为 1 的元素交换
	此时:[3, 1, 2, 0, 2, 5, 3]
	索引 0 的元素为 3
	将其与索引为 3 的元素交换
	此时:[0, 1, 2, 3, 2, 5, 3]
	索引 0 的元素为 0
	继续判断下一个
	... ... ... ... ... ... ...
	一直索引 4,其元素为 2
	则将其与索引为 2 的元素交换
	此时索引 2 的元素为 2
	与该数据相等,所以该数据重复,直接返回

题解:

//题解(菜逼思路解法)
class Solution {
    public int solution(int[] nums) {
        if (nums == null || nums.length < 2) {
            return 0;
        }
        Arrays.sort(nums);
        int index = 0;
        while (index < nums.length - 1) {
            if (nums[index] == nums[++index]) {
                break;
            }
        }
        return nums[index];
    }
}

//题解2(进阶思路解法)
class Solution {
    public int solution(int[] nums) {
        if (nums == null || nums.length < 2) {
            return 0;
        }
        int index = 0;
        int temp = 0;
        while (index < nums.length) {
            if (nums[index] != index) {
                if (nums[index] == nums[nums[index]]) {
                    break;
                }
                temp = nums[index];
                nums[index] = nums[nums[index]];
                nums[temp] = temp;
            } else {
                index++;
            }
        }
        return nums[index];
    }
}

04. 二维数组中的查找

题目描述:

在一个 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。

思路:认真看题目描述!!!

我的思路(菜逼思路):
暴力解法,直接遍历二维数组所有元素,查找是否存在目标元素(时间复杂度为 O(nm),空间复杂度为 O(1))

进阶思路(二叉查找树):
仔细观察示例矩阵,可发现,从右上角看的话,该矩阵类似于一棵二叉查找树(左子数的节点都小于根节点,右子树的节点都大于根节点),可以借助于二叉查找树的思想对目标元素进行查找(时间复杂度为 O(n+m),空间复杂度为 O(1)):
	如果目标元素小于当前元素,则往当前元素左侧查找;
	如果目标元素大于当前元素,则往当前元素右侧查找;
	如果目标元素等于当前元素,则返回true;
	如果最终未查找到目标元素,则返回false。

题解:

//题解1(菜逼思路解法)
class Solution {
	public boolean solution(int[][] nums,int target) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        for (int[] num : nums) {
            for (int i : num) {
                if (i == target) {
                    return true;
                }
            }
        }
        return false;
    }   
}

//题解2(进阶思路解法)
class Solution {
    public boolean solution(int[][] nums,int target) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        int i = 0;
        int j = nums[i].length - 1;
        while (i < nums.length && j >= 0) {
            if (nums[i][j] == target) {
                return true;
            } else if (nums[i][j] < target) {
                i++;
            } else {
                j--;
            }
        }
        return false;
}

05. 替换空格

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

示例:

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

思路:

我的思路:
没有找到思路!我可真是个小菜鸡!

思路 1:
创建一个 StringBuilder 对象,遍历字符串,如果不是空格,则直接拼接,如果是空格,则拼接 "%20",最后将 StringBuileder 转换成字符串返回即可

思路 2:
先遍历一遍字符串,查找其中空格个数
初始化一个字符数组,长度为字符串的长度加上两倍空格长度(空格本身占一个长度,这样才能放下替换空格的 "%20")
创建两个指针分别指向字符串的末尾以及数组的末尾
逆序遍历字符串,如果不是空格,则直接填入数组,如果是空格,则分别向数组中填入 '0'、'2'、'%'
最后将字符数组转为字符串返回即可

题解:

//题解1
class Solution {
    public String solution(String s) {
        if (s == null || s.length() == 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) != ' ') {
                sb.append(s.charAt(i));
            } else {
                sb.append("%20");
            }
        }
        return sb.toString();
    }
}

//题解2
class Solution() {
    public String solution(String s) {
        if (s == null || s.length() == 0) {
            return "";
        }
        int count = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == ' ') {
                count++;
            }
        }
        char[] ch = new char[s.length() + 2 * count];
        int i = s.length() - 1;
        int j = ch.length - 1;
        //因为前面数组的长度是绝对避免产生数组下标越界异常的
        //所以此处循环只需判断 i 即可
        while (i >= 0) {
            if (s.charAt(i) != ' ') {
                ch[j--] = s.charAt(i--);
            } else {
                ch[j--] = '0';
                ch[j--] = '2';
                ch[j--] = '%';
                i--;
            }
        }
        return String.valueOf(ch);
    }
}

06. 从尾到头打印链表

题目描述:

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

示例:

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

思路:

我的思路:
先遍历链表,获取链表长度,创建数组
再次遍历链表,获取链表节点的数据,将其从后往前依次填入数组中

另一思路:
使用栈,先将链表节点全部存入栈中,然后依次取出,将值存入数组

题解:

//题解1
class Solution {
    public int[] solution(ListNode head) {
        ListNode node = head;
        int len = 0;
        while (node != null) {
            len++;
            node = node.next;
        }
        int[] nums = new int[len];
        node = head;
        while (node != null) {
            nums[--len] = node.val;
            node = node.next;
        }
        return nums;
    }
}

//题解2
class Solution {
    public int[] solution(ListNode head) {
        Stack<ListNode> stack = new Stack<>();
        ListNode node = head;
        while (node != null) {
            stack.push(node);
            node = node.next;
        }
        int[] nums = new int[stack.size()];
        int index = 0;
        while (stack.size() != 0) {
            nums[index++] = stack.pop().val;
        }
        return nums;
    }
}

10. 斐波拉契数列【青蛙跳台阶】

题目描述:

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

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

示例:

输入:n = 2
输出:1

输入:n = 5
输出:5

思路:

我的思路(递归):
最容易想到的肯定是递归了,但是递归会随着传入参数值的增大,程序执行的时间呈几何级数递增

进阶思路(动态规划):
状态定义: 设 dp 为一维数组,其中 dp[i] 的值代表 斐波那契数列第 i 个数字 。
转移方程: dp[i + 1] = dp[i] + dp[i - 1] ,即对应数列定义 f(n + 1) = f(n) + f(n - 1) ;
初始状态: dp[0] = 0, dp[1] = 1 ,即初始化前两个数字;
返回值: dp[n] ,即斐波那契数列的第 n 个数字。
由于 dp 列表第 i 项只与第 i-1 和第 i-2 项有关,因此只需要初始化三个整形变量 res, pre, temp,利用辅助变量 res 使 pre, temp 两数字交替前进即可.

题解:

//题解1
class Solution {
    public int solution(int n) {
        if (n == 1) return 1;
        if (n == 0) return 0;
        return solution(n - 1) + solution(n - 2);
    }
}

//题解2
class Solution {
    public int solution(int n) {
        int res = 0;
        int pre = 1;
        int temp = 0;
        for (int i = 0; i < n; i++) {
            res = temp + pre;
            temp = pre;
            pre = res;
        }
        return temp;
    }
}

11. 旋转数组的最小数字

题目描述:

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

示例:

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

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

思路:

我的思路 1:
先将数组排序,然后输出数组的第一个元素(其实没有意义,因为本来就是已排序的数组的旋转,此处不再实现)

我的思路 2:
该数组是已经排好序数组的旋转,则虽然旋转后位置虽然变了,但是除了旋转位置外,其他位置都是前一个元素小于等于后一个元素,所以我们只需找到旋转位置,即为最小元素;如果没找到这个位置,则说明当前数组并未发生旋转,所以最小数字就是数组第一个元素

进阶思路(二分查找):
每次判断数组中间元素与最右侧元素的大小关系(只能与右侧比较,旋转后,小元素都会转到右侧,我一开始就是尝试的与左侧元素比较,结果测试用例 [1, 3, 5] 没通过):
	如果中间元素大于最右侧元素,则说明最小数字在右侧,更新左侧索引为中间索引加1
	如果中间元素小于最右侧元素,则说明最小数字在左侧,更新右侧索引为中间索引
	如果中间元素等于最右侧元素,此时无法判断最小数字的区域,所有,将右侧索引左移,再重新比较

题解:

//题解1
class Solution {
    public int solution(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        for (int i = 0; i < nums.length - 1; i++) {
            if (nums[i] > nums[i + 1]) {
                return nums[i + 1];
            }
        }
        return nums[0];
    }
}

//题解2
class Solution {
    public int solution(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int l = 0;
        int r = nums.length - 1;
        int mid = 0;
        while (l < r) {
            mid = l + (r - l) >> 2;
            if (nums[mid] > nums[r]) {
                l = mid + 1;
            } else if (nums[mid] < nums[l]) {
                r = mid;
            } else {
                r--;
            }
        }
        return nums[l];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值