剑指offer(五):查找算法
题目一:数组中重复的数字
方法一:哈希表 / Set,自己也是这么出来的,但是没有答案简便
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;
}
}
方法二:原地交换
遍历数组并通过交换操作,使元素的 索引 与 值 一一对应,就能通过索引映射对应的值,起到与字典等价的作用。
class Solution {
public int findRepeatNumber(int[] nums) {
int i = 0;
while(i < nums.length) {
//遍历到索引与对应的值相等是,跳过
if(nums[i] == i) {
i++;
continue;
}
//如果遍历到某一个值时,下式相等时nums[i]处和索引i处的元素值都为nums[i],即找到一组重复值,返回此值
if(nums[nums[i]] == nums[i]) return nums[i];
int tmp = nums[i];
nums[i] = nums[tmp];
nums[tmp] = tmp;
}
return -1;
}
}
题目二: 二维数组中的查找
在排好序的二维数组进行查找某一个数字,笨方法使用对每一行进行二分查找,时间复杂度为NlogN,相比于暴力方案的N^2要好不少;
class Solution {
int[][] matrix;
int n,target;
public boolean findNumberIn2DArray(int[][] matrix, int target) {
this.matrix = matrix;
int m = matrix.length;
if (m==0)return false;
this.n = matrix[0].length;
if (n==0)return false;
this.target = target;
for (int i = 0; i < m; i++) {
if (matrix[i][n-1]<target)continue;
else {
//对每一行进行判断
if (recursionBinarySearch(matrix[i],target,0,matrix[i].length-1)>=0)return true;
}
}
return false;
}
//进行二分查找
int recursionBinarySearch(int[] arr,int key,int low,int high){
if(key < arr[low] || key > arr[high] || low > high){
return -1;
}
int middle = (low + high) / 2; //初始中间位置
if(arr[middle] > key){
//比关键字大则关键字在左区域
return recursionBinarySearch(arr, key, low, middle - 1);
}else if(arr[middle] < key){
//比关键字小则关键字在右区域
return recursionBinarySearch(arr, key, middle + 1, high);
}else {
return middle;
}
}
}
方法二:类似于树的访问方式,直呼高级
思路:从 “根节点” 开始搜索,遇到比 target
大的元素就向左,反之向右,即可找到目标值 target
。
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
int i = matrix.length - 1, j = 0;
//i最多减小到0,j最多为行长度减一
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;
}
}
题目三:旋转数组的最小数字
使用暴力法竟然跑通了,时间复杂度也挺好,只是这样做不合适,正确解答使用二分查找
方法一:暴力法
class Solution {
public int minArray(int[] numbers) {
if (numbers.length==0)return 0;
int res=0;
for (int i = 0; i < numbers.length-1; i++) {
if (numbers[i+1]-numbers[i]<0){
res = i+1;
break;
}
}
return numbers[res];
}
}
方法二:二分查找、
关键点
- 每次使用二分法的中点与右边界进行比较
- 当比较值相同时的缩小区间安全性
class Solution {
public int minArray(int[] numbers) {
int i = 0, j = numbers.length - 1;
while (i < j) {
int m = (i + j) / 2;
if (numbers[m] > numbers[j]) i = m + 1;
else if (numbers[m] < numbers[j]) j = m;
else {
int x = i;
for(int k = i + 1; k < j; k++) {
if(numbers[k] < numbers[x]) x = k;
}
return numbers[x];
}
}
return numbers[i];
}
}
题目四: 第一个只出现一次的字符
方法一:使用两个set和一个字符数组
class Solution {
public char firstUniqChar(String s) {
char reschar = ' ';
if (s.length()==0)return reschar;
char[] tem = new char[s.length()];
Set<Character> res = new HashSet<>();
Set<Character> res1 = new HashSet<>();
for (int i = 0; i < s.length(); i++) {
char cur = s.charAt(i);
if (res.contains(cur)){
res1.add(cur);
}
res.add(cur);
tem[i] = cur;
}
for (int j = 0; j < tem.length; j++) {
if (!res1.contains(tem[j])){
reschar = tem[j];
break;
}
}
return reschar;
}
}
方法二:哈希表–>HashMap<Character, Boolean> 一个存字符,一个存是否出现一次,太精妙了
class Solution {
public char firstUniqChar(String s) {
HashMap<Character, Boolean> dic = new HashMap<>();
char[] sc = s.toCharArray();
for(char c : sc)
dic.put(c, !dic.containsKey(c));
for(char c : sc)
if(dic.get(c)) return c;
return ' ';
}
}
方法三:有序哈希表–>LinkedHashMap
有序哈希表中的键值对是 按照插入顺序排序 的。基于此,可通过遍历有序哈希表,实现搜索首个 “数量为 1的字符”。
class Solution {
public char firstUniqChar(String s) {
//LinkedHashMap是有顺序的哈希表
Map<Character, Boolean> dic = new LinkedHashMap<>();
char[] sc = s.toCharArray();
for(char c : sc)
dic.put(c, !dic.containsKey(c));
//哈希表的访问方式值得学习
for(Map.Entry<Character, Boolean> d : dic.entrySet()){
if(d.getValue()) return d.getKey();
}
return ' ';
}
}
题目五:在排序数组中查找数字 I
使用二分查找的方式来操作来找到左右边界。关键怎么找到左右边界
class Solution {
public int search(int[] nums, int target) {
// 搜索右边界 right
int i = 0, j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] <= target) i = m + 1;
else j = m - 1;
}
int right = i;
// 若数组中无 target ,则提前返回
if(j >= 0 && nums[j] != target) return 0;
// 搜索左边界 right
i = 0; j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] < target) i = m + 1;
else j = m - 1;
}
int left = j;
return right - left - 1;
}
}
//上述方法比较臃肿
class Solution {
public int search(int[] nums, int target) {
return helper(nums, target) - helper(nums, target - 1);
}
int helper(int[] nums, int tar) {
int i = 0, j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] <= tar) i = m + 1;
else j = m - 1;
}
return i;
}
}
题目六:0~n-1 中缺失的数字
class Solution {
public int missingNumber(int[] nums) {
int i = 0, j = nums.length-1;
while (i<=j){
int m = (i+j)/2;
if (nums[m]==m)i=m+1;
else j=m-1;
}
return i;
}
}