剑指OFFER打卡
剑指 Offer 03. 数组中重复的数字
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
方法一: 暴力破解
class Solution {
public int findRepeatNumber(int[] nums) {
int[] q = new int[100005];
for(int i = 0; i < nums.length; i ++ ) {
int res = nums[i];
q[res]++;
}
for(int i = 0; i < 100005; i ++ ) {
if(q[i] > 1) {
return i;
}
}
return 0;
}
}
方法二:哈希表
class Solution {
public int findRepeatNumber(int[] nums) {
Set<Integer> dic = new HashSet<>();
for(int num : nums) {
if(dic.contains(num)) return num;
dic.add(num);
}
return -1;
}
}
方法三:原地交换
思路
(原地交换) O(n)
首先遍历一遍数组,如果存在某个数不在0到n- 1的范围内,则返回-1。下面的算法的主要思想是把每个数放到对应的位置上,即让nums[i] = i.从前往后遍历数组中的所有数,假设当前遍历到的数是nums[i]=x , 那么:如果x!=i&&nums[x]==x,则说明x出现了多次,直接返回x即可;如果nums[x]!=x那我们就把x交换到正确的位置上,即swap(nums[x], nums[i]),交换完之后如果 nums[i] != i , 则重复进行该操作。由于每次交换都会将-个数放在正确的位置上,所以swap 操作最多会进行n次,不会发生死循环。循环结束后,如果没有找到任何重复的数,则返回-1。
class Solution {
public int findRepeatNumber(int[] nums) {
for(int i = 0; i < nums.length; i ++ ) {
//顺序不能变
// [3, 4, 2, 1, 1, 0] 如果最后一个return的方法放在前面
// 会返回2 ,但结果是1,产生错误,
// 所以要放在最后判断
if(nums[i] != i && nums[nums[i]] != nums[i]) {
int t = nums[i];
nums[i] = nums[t];
nums[t] = t;
}
if(nums[i] == i) {
continue;
}
if(nums[nums[i]] == nums[i]) {
return nums[i];
}
}
return -1;
}
}
时间复杂度分析: 每次swap 操作都会将一个数放在正确的位置 上,后-次swap 会将两个数同时放到正确位置上,一只有n个数和n个位置,所以swap最多会进行n-1 次。所以总时间复杂度是O(n) 。
剑指 Offer 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(n + m)
在m * n矩阵matrix中我们可以发现一个性质:对于每个子矩阵右上角的数x,x左边的数都小于等
于x,x下边的数都大于x。
- 如果x大于target ,则x下边的数-定都大于target, 我们可以直接排序当前一 整列的数;
排除一整行就是让枚举的点的横坐标加一, 排除- 整列就是让纵坐标减一 。 当我们排除完整 个矩阵后仍没有找到目
标值时,就说明目标值不存在,返迥false。
具体过程如下: - 1、初始化i = 0,j=matrix[0].size() - 1。
- 2、如果matrix[i][j] == target 返回 true。
- 3、如果matrix[i][j] < target ,i++ ,排除一行。
- 4、如果matrix[i][j] > target ,j-- ,排除一列。
- 5、如果出界还未找到target, 则返回false 。
因此我们可以从整个矩阵的右上岍始枚举,假设当前枚举的数是x :
- 如果x等于target ,则说明我们找到了目标值,返迥true ;
- 如果x于target ,则x左边的数一定都小于target ,我们可以直接排除当前一整行的数;
- 如果x大于target ,则x下边的数-定都大于target, 我们可以直接排序当前一 整列的数;
排除一整行就是让枚举的点的横坐标加一, 排除- 整列就是让纵坐标减一 。 当我们排除完整 个矩阵后仍没有找到目标值时,就说明目标值不存在,返迥false。
具体过程如下:
- 1、初始化i = 0,j=matrix[0].size() - 1。
- 2、如果matrix[i][j] == target 返回 true。
- 3、如果matrix[i][j] < target ,i++ ,排除- 行。
- 4、如果matrix[i][j] > target ,j-- ,排除一列。
- 5、如果出界还未找到target, 则返回false 。
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
// 以左下角作为二插树的根节点
int i = matrix.length - 1;
int j = 0;
while(i >= 0 && j < matrix[0].length) {
if(matrix[i][j] > target) i --;
else if(matrix[i][j] < target) j ++ ;
else return true;
}
return false;
}
}
时间复杂度分析: 每一步排除一行或者一列, 矩阵一共有n行, m列, 最多会进行 n + m 步,所以时间复杂度是O(n + m);
方法二: 二分
模板套一套, 不懂得兄弟们可以看我的另一篇文章, 2个模板解决所有二分
第一种二分: 右边界
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if(matrix.length == 0 || matrix[0].length == 0) return false;
int m = matrix.length;
int n = matrix[0].length;
for(int i = 0; i < m; i ++ ) {
if(check(matrix[i], target)) return true;
}
return false;
}
private boolean check(int[] q, int target) {
int l = 0, r = q.length - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (q[mid] <= target) l = mid;
else r = mid - 1;
}
if (q[l] == target) return true;
else return false;
}
}
第二种二分: 左边界
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if(matrix.length == 0 || matrix[0].length == 0) return false;
int m = matrix.length;
int n = matrix[0].length;
for(int i = 0; i < m; i ++ ) {
if(check(matrix[i], target)) return true;
}
return false;
}
private boolean check(int[] q, int target) {
int l = 0, r = q.length - 1;
while (l < r) {
int mid = l + r >> 1;
if (q[mid] >= target) r = mid;
else l = mid + 1;
}
if (q[l] == target) return true;
else return false;
}
}
剑指 Offer 05. 替换空格
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
输入:s = “We are happy.”
输出:“We%20are%20happy.”
限制:
0 <= s 的长度 <= 10000
方式一: 字符拼接
思路
(线性扫描) O(n)
这个题在C+ +里比较好做,我们可以从前往后枚举原字符串:
- 1、如果遇到空格,则在string类型的答案中添加"%20" ;
- 2、如果遇到其他字符,则直接将它添加在答案中;
class Solution {
public String replaceSpace(String s) {
String s1 = "";
char[] c1 = s.toCharArray();
for(int i = 0; i < c1.length; i ++ ) {
if(c1[i] == ' ') {
s1 = s1 + "%20";
} else {
s1 += c1[i];
}
}
return s1;
}
}
时间复杂度分析: 原字符串只会被遍历常数次,所以总时间复杂度是O(n) 。
方式二 :遍历添加
class Solution {
public String replaceSpace(String s) {
StringBuilder res = new StringBuilder();
for(Character c : s.toCharArray())
{
if(c == ' ') res.append("%20");
else res.append(c);
}
return res.toString();
}
}