算法-查找算法-offer03-offer53I-offer53II-offer04-offer11-offer50

这篇博客详细介绍了多种查找算法的应用,包括数组找重复数字、在排序数组中查找数字、寻找缺失的数字、二维数组查找、旋转数组找最小值以及字符串中首次出现的独特字符。方法涵盖了哈希集合、双层循环、数组重排、二分查找、位运算等技巧。此外,还讨论了删除数组重复元素的双指针策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

查找算法

二分法

在这里插入图片描述
在这里插入图片描述
while循环结束条件是l>=r,为什么二分结束时我们优先取r而不是l?
循环结束的时候l>r 此时可能发生越界,因此二分问题优先取r不会翻车。

offer03数组找重复

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

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

方法1:hashset集合

class Solution {
    public int findRepeatNumber(int[] nums) {
        //使用set集合的不存储重复元素的性质
        Set<Integer> set = new HashSet<Integer>();

        int result = 0;
        for(int num : nums){
            if(!set.add(num))
                result = num;
        }
        return result;
    }
}

方法2:双层for循环

class Solution {
    public int findRepeatNumber(int[] nums) {

        // 双层for循环遍历
        int result = -1;
        for(int i = 0; i < nums.length-1 ; i++){
            for(int j = i+1 ; j < nums.length;j++){
                if(nums[i] == nums[j]){
                    result = nums[i];
                    break;
                }
            }
            if(result != -1)
                break;
        }
        return result;
    }
}

方法3:数组重排

分析
将数组重新排列成,数组下标与数组内容相同的形式。
下标i 对应的值num[i] 判断是否相等,相等则继续判断下一个;
不相等,判断下标num[i] 与num[num[i]] 的值是否相等,相等,则说明有重复元素输出nums[i];
不相等,则交换两个数。

class Solution {
    public int findRepeatNumber(int[] nums) {
    	int i = 0 ; 
    	int temp = -1;
    	while( i < nums.length){
    		if(nums[i] == i){
    			i++;
    			continue;
    		}
    		if(nums[i) == nums[nums[i]]) return nums[i];
    		temp = nums[i];
    		nums[i] = nums[temp];
    		nums[temp] = temp;
    	}
    	return -1;
    }
}

图示
在这里插入图片描述

offer53 在排序数组中查找数字I

需求
统计一个数字在排序数组中出现的次数。

示例
输入: nums = [5,7,7,8,8,10], target = 8 输出: 2
输入: nums = [5,7,7,8,8,10], target = 6 输出: 0

方法1:循环对比

class Solution {
    public int search(int[] nums, int target) {
        int i = 0 ;
        int count = 0;
        while(i < nums.length){
            if(nums[i] < target){
                i++;
                continue;
            }else if(nums[i] == target){
                i++;
                count++;
                continue;
            }else{
                break;
            }
        }
        return count;
    }
}

方法2:Map集合

class Solution {
    public int search(int[] nums, int target) {
        // 使用Map集合
        Map<Integer,Integer> map = new HashMap<>();
        map.put(target,0);

        int value = -1;
        for(int num:nums){
            if(map.containsKey(num)){
                value = map.get(num) + 1 ;
                map.put(num,value);
            }
                
        }
        return map.get(target);
    }
}

方法3:二分法(找右节点)

class Solution {
    public int search(int[] nums, int target) {
        return searchBorder(nums,target) - searchBorder(nums,target-1);
    }
    public int searchBorder(int[] nums,int target){
        int left = 0;
        int right = nums.length-1;
        int mid = 0;
        while(left <= right){
            mid = (right + left) /2;
            if(nums[mid] <= target)
                left = mid+1;
            else
                right = mid -1;
        }
        return right;
    }
}

方法4:二分法(找左右节点)

在这里插入代码片class Solution {
    public int search(int[] nums, int target) {
        // 二分法 找左边界 右边界
        int i = 0 , j = nums.length-1;
        int mid = 0;
        while( i <= j){
            mid = (i+j)/2;
            if(nums[mid] < target)
                i = mid + 1;
            else
                j = mid -1;
            
        }
        int left = j;
        // 数组无target情况
        if( i <= nums.length-1 && nums[i] != target) return 0;

        // 找右节点
        i = 0;
        j = nums.length-1;
        while(i <= j){
            mid = (i+j)/2;
            if(nums[mid] <= target)
                i = mid+1;
            else    
                j = mid -1;
        }
        int right = i;
        return right - left - 1 ;
    }
}

图示
在这里插入图片描述

offer53II 0~n-1中缺失的数字

需求
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

示例
输入: [0,1,3] 输出: 2
输入: [0,1,2,3,4,5,6,7,9] 输出: 8

方法1:循环比较

通过比较元素与对应的下标 是否一样,不一样返回下标;
都一样,说明没有数组长度n这个元素,返回n。

class Solution {
    public int missingNumber(int[] nums) {
        // 遍历数组比较
        int n = nums.length;
        for(int i = 0 ; i < n ; i++){
            if(i != nums[i]) return i;
        }
        return n;
    }
}

时间复杂度是:O(n) for循环查找;
空间复杂度是:O(n) nums数组的长度。

方法2:数组之和(数字)

求数组元素之和、数组长度n的1-n之间的元素之和。两个相减就是少的元素。

class Solution {
    public int missingNumber(int[] nums) {
        // 求数组长度n的元素之和
        int n = nums.length;
        int sum0=0,sum1 = 0;
        // 求1..n的和
        sum0 = n*(n+1)/2;
        // 求给定数组元素之和。
        for(int num :nums)
            sum1 += num;
        return sum0-sum1;
    }
}

方法3:位运算

采用异或的形式,相同元素为0,不同为1.
由于添加元素0-n 与数组元素进行异或,缺失的元素是一个,则会被保留下来。

class Solution {
    public int missingNumber(int[] nums) {
        // 位运算
        int result = 0;
        // 数组元素异或
        for(int i = 0 ; i < nums.length ; i++)
            result ^= nums[i];
        // 0-n之间元素异或
        for(int i = 0; i < nums.length+1 ; i++)
            result ^= i;
        return result;
    }
}

方法4:二分法

分析
数组被分为左子数组、右子数组,缺失的数字是右子数组的首个元素对应的索引x

class Solution {
    public int missingNumber(int[] nums) {
        // 二分法
        int i = 0 , j = nums.length-1;
        int mid = 0;
        while(i <= j){
            mid = (i+j) /2;
            if(nums[mid] == mid) // 说明在右侧
                i = mid +1 ;
            else // 说明在左侧
                j = mid -1 ;
        }
        return j+1;
    }
}

时间复杂度:O(logN)
空间复杂度:O(N)

offer04 二维数组中的查找

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

示例

[
  [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
方法1:for循环+break
class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
         // 特殊值
         // 这个位置需要先判断matrix是否为null 如果直接计算length 可能会出错,比如它是一维数组且为空 那么matrix[0].length会报错。
         if(matrix == null || matrix.length == 0 || matrix[0].length == 0)
            return false;

        int i = 0;
        while(i < matrix.length){
            int j = 0;
            while(j < matrix[0].length){
                if(matrix[i][j] == target)
                    return true;
                else if(matrix[i][j] > target)
                    break;
                j++;
            }
            i++;
        }
        return false;
    }
}
方法2:二叉搜索树

分析
在这里插入图片描述
代码:

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        // 二叉树搜索
        if(matrix == null || matrix.length==0 || matrix[0].length ==0 )
            return false;
        int i = 0,j = matrix[0].length-1;
        while(i < matrix.length && j >= 0){
            if(matrix[i][j] > target) j--;
            else if(matrix[i][j] < target) i++;
            else return true;
        }
        return false;
    }
}

offer11 旋转数组的最小值

需求
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为 1。
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。

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

方法1:for循环(倒叙查找)

分析
由于原始数组从小到大排序,之后又进行前面一部分内容旋转到后面,整体会出现先增大 后分界 然后 再增大的过程。
从后向前找或者从前向后找 不是递增的情况 输出即可。

代码

class Solution {
    public int minArray(int[] numbers) {
        int n = numbers.length;
        if(n == 1)
            return numbers[0];

        for(int i = n-1; i >0 ; i--){
            if(numbers[i-1] > numbers[i])
                return numbers[i];
        }
        return numbers[0];
    }
}
方法2:二分法

图示
在这里插入图片描述

代码

class Solution {
    public int minArray(int[] numbers) {
        // 二分法
        int i = 0 ,j = numbers.length-1;
        int m;
        while(i < j){
            m = (i + j) / 2;
            if(numbers[m] > numbers[j])
                i = m+1;
            else if(numbers[m] < numbers[j])
                j = m;
            else
                j = j-1;
        }
        return numbers[i];
    }
}

offer50 第一次只出现一次的字符

总结
存储出现个数、位置索引。

需求
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

示例
输入:s = “abaccdeff” 输出:‘b’
输入:s = “” 输出:’ ’

方法1:使用Map(value是元素个数)
class Solution {
    public char firstUniqChar(String s) {
        // 使用Map集合吧
        Map<Character,Integer> map = new HashMap<Character,Integer>();

        for(int i = 0 ; i < s.length();i++){
            Character key = s.charAt(i);
            if(map.containsKey(key)){
                Integer value = map.get(key);
                map.put(key,value+1);
            }else{
                map.put(key,1);
            } 
        }

        // 遍历找value = 1
        for(int i = 0; i < s.length() ; i++){
            Integer value = map.get(s.charAt(i));
            if(value == 1)
                return s.charAt(i);
        }
        return ' ';
    }
}
方法2:indexOf 和 lastIndexOf 方法
class Solution {
    public char firstUniqChar(String s) {
        // 使用indexOf方法 和 lastIndexOf方法
        for(int i = 0 ; i < s.length() ; i++){
            if(s.indexOf(s.charAt(i)) == s.lastIndexOf(s.charAt(i)))
                return s.charAt(i);
        }
        return ' ';
    }
}
方法3:使用Map(value是元素索引)
class Solution {
    public char firstUniqChar(String s) {
        // 使用Map存储存储索引,如果有重复值为-1,没有重复值为下标
        Map<Character,Integer> map = new HashMap<Character,Integer>();

        for(int i = 0; i < s.length(); i++){
            Integer value = map.get(s.charAt(i));
            if(map.containsKey(s.charAt(i)))
                map.put(s.charAt(i),-1);
            else
                map.put(s.charAt(i),i);
        }

        // 循环遍历
        for(int i = 0 ; i < s.length() ; i++){
            if(map.get(s.charAt(i)) != -1)
                return s.charAt(i);
        }

        return ' ';
    }
}
方法4:使用数组存储个数
class Solution {
    public char firstUniqChar(String s) {
        // 字符串 转字符数组  用另一个数组存储元素出现的位置,
        char[] arr = s.toCharArray();
        // 存储26个字母在字符串中出现的次数。
        int[] arrint = new int[26];
        int index;
        for(int i = 0 ; i < arr.length;i++){
            index = arr[i] - 'a';
            arrint[index]++;
        }

        // 遍历arrint数组 
        for(int i = 0 ; i < arr.length;i++){
            if(arrint[arr[i]-'a'] == 1)
                return arr[i];
        }
        return ' ';
    }
}
方法5:LinkedHashMap(有序的map)
class Solution {
    public char firstUniqChar(String s) {
        // 使用具有排序功能的LinkedHashMap
        Map<Character,Boolean> map = new LinkedHashMap<Character,Boolean>();
        char[] arr = s.toCharArray();

        // 存储map
        for(char ch:arr){
            if(map.containsKey(ch))
                map.put(ch,!map.containsKey(ch));
                // map的值存储的value 表示该键是否是唯一的。有重复=false 没有重复是ture
            else
                map.put(ch,true);
        }

        // 遍历map
        for(Map.Entry<Character,Boolean> mapch :map.entrySet()){
            if(mapch.getValue()) return mapch.getKey();
        }
        return ' ';
    }
}

两数之和 初级算法

需求
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那两个整数,并返回它们的数组下标。
假设每种输入只会对应一个答案 = target的两数之和只有一个解,且每组输入都有解。
数组中同一个元素在答案里不能重复出现 = 结果返回的数组下标不能重复。

解题思路

假设所需的整数数组是sums、整数目标值是target
根据题目内容,每种输入都有唯一对应的答案,也就是说只要输入的值就有解,就会存在一种特殊的解,输入数组只有两个元素,则这两个元素就是它的解。所以需要在每个方法中都需要添加如下代码:

int[] result = {0,1}
if(nums.length <= 2)
{
	return result;
}

冒泡排序:使用的是两层循环
for(int i = 0 ; i < sums.length ; i ++)
for( int j = i+1 ; j>= i ; j++)

哈希表的形式(没有提前将数组的值存储在哈希表中)
定义哈希表 Map<Integer,Integer> map = new HashMap<Integer,Integer>() 其中键存储的是数组元素,值存储的是数组的下标;
遍历数组i,获取每一个元素对应的target-sums[i]的值,判断与哈希表中的元素是否一致,map.containsKey(target-sums[i])
一致,返回值是new int[] {map.get(target-sums[i]) ,i};不一致,不执行任何操作
最终如果在if中没有返回值,则return new int[] {};

二分法,在冒泡排序的基础之上,内层循环使用两个指针
外层循环for(int i = 0 ; i < sums.length ; i++)
内存循环for(int j = i+1 , k = sums.length-1 ; j<= k ; j++,k–)

四分法,在二分法的基础上,外层循环也采用两个指针
外层循环for(int i = 0 ,j = sums.length-1 ; i <= j ; i++,j–)
内层循环for(int a = i+1,b=j-1; a <= b ; a++,b–)

二分法 + 哈希表
定义哈希表 Map<Integer,Integer> map = new HashMap<Integer,Integer>(); key=数组的值,value=值对应的下标
循环遍历数组,使用两个指针,for(int i = 0,j = nums.length-1 ; i <= j ; i++,j–)
比较target-nums[i] 或者 target-nums[j] 的值是否在哈希表中。 map.containsKey(target-nums[i]) 或者 map.containsKey(target-nums[j])
存在就给result数组赋值。不存在就把数组元素存储在哈希表中 map.put(nums[i],i) 或者 map.put(nums[j],j)

代码细节
main函数
public static void main(String[] args) {
        int[] array = {2,7,11,15};
        int target = 9;
//        int[] array = {3, 2, 4};
//        int[] array = {3,3};
        System.out.println("冒泡排序");
        method1(array, target);
        System.out.println();

        System.out.println("哈希表");
        int[] result2 = method2(array, target);
        System.out.println("[" + result2[0] + "," + result2[1] + "]");

        System.out.println("二分法检索");
        int[] result3 = method3(array, target);
        System.out.println("[" + result3[0] + "," + result3[1] + "]");

        System.out.println("四分法检索");
        int[] result4 = method4(array, target);
        System.out.println("[" + result4[0] + "," + result4[1] + "]");

        System.out.println("二分法 + 哈希表");
        int[] result5 = method4(array, target);
        System.out.println("[" + result5[0] + "," + result5[1] + "]");

    }
冒泡排序
 public static void method1(int[] array, int target) {
        for (int i = 0; i < array.length; i++) {
            for (int j = i + 1; j < array.length; j++) {
                if (array[i] + array[j] == target) {
                    System.out.print("[" + i + "," + j + "]");
                }
            }
        }
    }
     //分析,时间复杂度为O(N^2),空间复杂度为O(1)

2022/6/3

public int[] twoSum(int[] nums, int target) {
        // 边界条件 只有两个元素
        if(nums.length == 2)
            return new int[]{0,1};
            
        //双层for循环
        for(int i = 0 ; i < nums.length-1 ; i++){
            for(int j = i+1 ;j < nums.length ; j++){
                if(nums[j] + nums[i] == target){
                    return new int[]{i,j};
                }
            }
        }
        return new int[]{};
    }
哈希表 containsKey、key=数组值、value=值下标
public static int[] method2(int[] array, int target) {
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i = 0; i < array.length; i++) {
            if (map.containsKey(target - array[i])) {
                return new int[]{map.get(target - array[i]), i};
            }
            map.put(array[i], i);
        }
        return new int[]{};
    }

    //分析:使用哈希查找时间复杂度是O(1),在加上单层for遍历N个变量,则整体时间复杂度是O(N)

时间是:1ms,内存消耗是:41.4M

二分法,主要用在第二层循环的比较
public static int[] method3(int[] nums, int target) {
        for (int i = 0; i < nums.length; i++) {
            for (int j = i, k = nums.length - 1; j <= k; j++, k--) {
                if ((target - nums[i]) == nums[j]) {
                	return new int[]{i,j};
                } else if ((target - nums[i]) == nums[k]) {
                	return new int[]{i,k};
                }
            }
        }
        return new int[]{};
    }
    //分析: 整体的时间复杂度是O(nlogn)

时间:37ms,内存消耗:41.8M

四分法(数组遍历二分 + 找target之和遍历再二分)
public static int[] method4(int[] nums, int target) {
        for (int i = 0, j = nums.length - 1; i <= j; i++, j--) {
        	if(nums[i] + nums[j] == target){
        		return new int[]{i,j};
        	}
            for (int a = i + 1, b = j - 1; a <= b; a++, b--) {
                if (nums[i] + nums[a] == target) {
                	return new int[]{i,a};
                } else if (nums[j] + nums[a] == target) {
                	return new int[]{a,j};
                } else if (nums[i] + nums[b] == target) {
                	return new int[]{i,b};
                } else if (nums[j] + nums[b] == target) {
                	return new int[]{b,j};
                } 
            }
        }
        return new int[]{};
    }
// 分析:时间复杂度是O(n/2logn)

时间:0ms,内存消耗:41.4M

备注2022/6/3
第二层for循环的两个变量的判断的条件得是 a <= b
否则对于奇数数组的话,最中间的数据没有办法比较了;
比如:{3,2,4},i = 0 j = 2 此时a = 1 b = 1 如果没有 a <= b这个条件,而是 a < b 的情况,这样中间的值i = 1 就没有办法比较了;

二分法 + 哈希表
     public int[] twoSum(int[] nums, int target) {
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        for(int i = 0,j = nums.length-1; i <= j; i++,j--){
            if(map.containsKey(target-nums[i])){
                return new int[]{map.get(target-nums[i]),i};
            }else if(map.containsKey(target-nums[j])){
                return new int[]{map.get(target-nums[j]),j};
            }else if(nums[i] + nums[j] == target){
                return new int[]{i,j};
            }
            map.put(nums[i],i);
            map.put(nums[j],j);

        }
        return new int[]{};
    }

时间:0ms,内存损耗:41.5M。

注意
2022/5/6二分法要注意对于端点的思考。也就是 i 和 j 之和等于target的情况,因为在第二层遍历的时候,便没有进行两者的比较
2022/6/3 注意数组奇数个数的情况,循环的条件得是 i <= j 而不是 i < j

删除数组中的重复项

需求
一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
元素的 相对顺序 应该保持 一致 。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。
将最终结果插入 nums 的前 k 个位置后返回 k 。
不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

思路
双指针

使用双指针的形式
一个指针表示可以被覆盖的位置slow,另一个表示比较元素fast,两个指针的初始值都是从1开始的。
当nums[slow-1] != nums[fast] 的时候,说明标号0和标号1的内容不一样,所以执行nums[slow] = nums[fast] 的赋值操作,并执行slow++、fast++
内容相同,则将fast++

代码
双指针

方式1:

    public static int removeDuplicates(int[] nums) {
        // 指针i表示可以被覆盖的位置,
        // 指针j表示后续查找元素
        int n = nums.length;
        if(n == 0){
            return 0;
        }
        int fast = 1,slow = 1;
        while(fast<n) {
            if (nums[fast] != nums[slow - 1]) {
                nums[slow] = nums[fast];
                ++slow;
            }
            ++fast;
        }
        return slow;
    }

备注2022/6/3
注意两个指针是从1的索引开始的,而不是slow = 0,fast = 1;

方式2:使用了for循环,但是在leecode系统中提交没有问题,但是自己电脑运行有问题。

输出结果正确是:5
但是实际输出结果是:8
// idea_face - arrayAndMatrix
public static int removeDuplicates2(int[] nums) {
        if(nums.length == 0 || nums == null){
            return 0;
        }
        int left = 0;
        for(int right = 1; right <nums.length;right++){
            if(nums[left] != nums[right]){
                nums[++left] = nums[right];
            }
            System.out.println(left + "," + right);
        }
        return ++left;
    }
   

方法3:

public int removeDuplicates(int[] nums) {
	// 定义待覆盖元素位置
	int t = 0;
		
	for(int i = 0 ; i <nums.length ; i++){
		if( i == 0 || nums[i] != nums[i-1] ) nums[t++] = nums[i];	
	}

	return t;	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值