剑指Offer-数组去重

22 篇文章 3 订阅
21 篇文章 1 订阅

1,查找数组中的重复数字

  • 题目描述:

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

1.1,思路一

  • 这个题目是一道简单题目,最容易想到的是对数组中的元素一趟排序操作,然后在扫描一趟数组,就可以查找出重复的元素。但是这样的话排序是这里面最耗时间的地方,排序一个长度为n的数组的时间复杂度是o(nlogn)
  • 代码实现:
/**
     * 排序方法查找数组中的重复元素
     * @param arr 待排序数组
     * @return 查找到重复元素就返回,否则返回-1
     */
    public static int duplicate02(int []arr){
        int len=arr.length;
        if(len == 0){
            return -1;
        }
//        使用工具类,对数组进行排序操作
        Arrays.sort(arr);
        for(int i=1;i<len;i++){
            if(arr[i] == arr[i-1]){
                return arr[i-1];
            }
        }
        return -1;
    }
  • 时间复杂度:o(nlogn)

1.2,思路二

  • 方法二我们可以使用一个哈希表来实现算法,扫描一遍数组,如果当前元素已经存在于哈希表中,就把当前的元素返回,否则,就把当前元素添加到哈希表中,接着判断下一个元素,不过这样做是以牺牲空间为代价,换取时间的效率。
  • 代码实现:
/**
     * 使用哈希表,空间复杂度是o(n)
     * @param arr 待查找的数组
     * @return 查找到重复元素就返回,否则返回-1
     */
    public static int duplicate01(int []arr){
        int len=arr.length;
        if(len == 0)
            return -1;
//        创建一个哈希表,存储我们的数据
        Map hashTable=new Hashtable(len);
        for (int i=0;i<len;i++){
            if(hashTable.containsValue(arr[i])){
//                判断哈希表中是否包含元素,如果没有包含,就添加到哈希表中,如果包含,就把此元素返回
                return arr[i];
            }else {
                hashTable.put(i,arr[i]);
            }

        }
        return -1;
    }
  • 空间复杂度:o(n)
  • 时间复杂度:o(n)

1.3,思路三

  • 通过审题,我们发现数组中的数字都在0-n-1的范围之内,如果这个数组中没有重复的数字,那么每一个元素就可以对应的放在和其下表一样的位置,比如元素5就放在下表为5的位置。所以,基于此想法,我们可以对数组重拍操做,当我们扫描到下表为i的数字时候,首先比较这个数组(m)是否等于i,如果是,就接着比较下一个数字,如果不是,则再拿他和第m个数字进行比较,如果他和第m个数字相等,那么就找到一个重复的元素,如果和第m个元素不相等,就把第i个数字和第m个数字进行交换,把m放到属于他的位置,接下来重复这个比较,直到发现一个重估数字为止。
  • 代码实现
/**
     *
     * @param arr 待查找的数组
     * @return 查找到重复元素就返回,否则返回-1
     */
    public static int duplicate(int arr[]){

        int len=arr.length;
        if(len == 0){
            return -1;
        }
        int temp=0;
        for(int i=0;i<len;i++){
            temp = arr[i];
                while (temp != i) {
//                如果当前元素和元素的下表不是一样的,那么就把当前元素放入和其下表值一样的位置
//                    在这里是判断元素是否重复
                    if(arr[i] == arr[temp])
                        return arr[i];
//                    交换元素
                    arr[i] = arr[temp];
                    arr[temp] = temp;
                    temp = arr[i];
                }
            }
        return -1;
    }

  • 时间复杂度:0(n).

1.4,思路四

  • 使用一个辅助的数组,数组默认初始化为0,然后遍历数字数组,每遇到一个数字,就把这个数字对应的空数组里面的数值修改为1,当再次遍历到这个数值时,说明重复,直接的返回即可。
  • 代码实现
class Solution {
        public int findRepeatNumber(int[] nums) {
            int [] tempArr = new int[nums.length];
            for (int i = 0; i < nums.length; i++) {
                if (tempArr[nums[i]] == 0) {
                    tempArr[nums[i]] = 1;
                } else {
                    return nums[i];
                }
            }
            return 0;
        }
    }
  • 时间复杂度:0(n)

2,二维数组中元素查找

  • 题目描述

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

2.1,思路一

  • 通过审题,简单判断一下就是在二维数组中查找一个元素值,很自然的可以想到通过一个双层循环,逐个元素的遍历进行查找,但是时间复杂度比较的高,达到n的平方级别。
  • 代码实现
/**
     * 二维数组中元素查找
     * @param arr 待查找的数组
     * @param num 查找元素
     * @return 查找成功返回元素值,否则返回-1
     */
    public static int searchNum01(int [][]arr,int num){
        for(int i=0;i<arr.length;i++){
            for(int j=0;j<arr[0].length;j++){
                if(arr[i][j] == num){
                    return arr[i][j];
                }
            }
        }

        return -1;
    }
  • 时间复杂度:o(n2)

2.2,思路二

  • 思路一的解答方式很容易想到,但是我们写程序的要求是尽量的避免时间和空间复杂度很高的算法,所以尽管方法一容易想到,但是我们还是不使用此方法。题目中说到,在一个n*m的二维数组中,每一行元素从左到右都是递增的顺序,每一列元素从上到下也是递增的,所以这里是我们的解题点,如果我们使用第一种方式查找元素,那么我们每一次只能排除一个元素,但是我们能不能一次比较,排除大于一个的元素呢?实际是可以的,二维数组中第一行元素,是每一列中元素值最小的,所以,我们可以以列为单位,把我们要查找的元素和每一列的第一个元素比较,如果当前需要查找的元素小于某一列的第一个元素,那么此时我们就可以把当前的一整列元素全部排除掉,接着判断下一列,如果当前元素大于某一列的第一个元素,那么我们就可以在行的基础上比较下一个元素,逐个判断每一行的元素,这样,从列的角度看,好的情况下,我们每一次比较可以排除一行的元素,效率大大的提高了。文字看起来不好理解,我们用图说话。

1607246771064

1607246800945

通过上面的例子,我们发现,一次判断,我们赛选掉的元素不在是一个,而是基于行或者列的排除,所以大大提高了算法的效率。

  • 代码实现
/**
     * 二位数组中元素的搜索
     * @param arr 待查找的数组
     * @param num 查找元素
     * @return 查找成功返回元素值,否则返回-1
     */
    public static int searchNum(int [][]arr,int num){
        int len=arr[0].length-1;
        int i=0,j=len;
//        控制二维数组下表,防止越界
        while ((i<arr.length)&&(j>=0)){
//            纵向判断
                if(arr[i][j] > num){
                    j--;
                }else {
//                    横向判断
                    if(num == arr[i][j]){
                        return arr[i][j];
                    }
                    i++;
                }
        }
        return -1;
    }
  • 时间复杂度:o(n)

3,空格替换

  • 题目描述

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

3.1,思路一

思路一可能是最容易想到的一种方式,当我们遇到一个空格之后,我们把当前空格之后的字符全部向后面移动两格,重复操作,直到我们遍历完毕字符串为止,但是这种方法的时间复杂度很高,是o(n2)级别,这种方式我们一般不推荐使用。

  • 时间复杂度:o(n2)

3.2,思路二

思路一的时间复杂度很高,能不能只对字符串遍历一遍,就可以把空格处全部填充完毕,当然可以,我们可以先对字符串做一遍遍历操作,统计出字符串中空格的个数,然后开辟新的数组空间,大小是字符串的长度+空格数的2倍,然后我们在从后向前遍历字符串,遍历的时候直接把字符逐个放入新的数组中,遇到一个空格之后,新数组就向前面移动三个空格,然后插入一个%20即可,这样,只需要对字符串进行一遍遍历操作即可填充,降低了时间复杂度。

  • 代码实现
public class ReplaceSpaceDemo {
    public static void main(String[] args) {
        char []ch={'w','e',' ','a','r','e',' ','h','a','p','p','y'};
//        System.out.println(ch.length);
        char c[]=replace(ch);
        System.out.println(Arrays.toString(c));
    }
	/**
     * 空格替换
     * @param ch 字符串 返回替换后的字符串
     * @return
     */
    public static char[] replace(char []ch){
//        首先获取字符串的长度
        int len=ch.length;
        int num=0;
//        统计空格的个数
        for(int i=0;i<len;i++){
            if(ch[i] == ' '){
                num++;
            }
        }
//        开辟一个新的字符串
        char c[]=new char[len+num*2];
        for (int j=c.length-1,i=ch.length-1;j>=0;j--){
            if(ch[i] != ' '){
//                如果当前字符不是等于空那么就复制到新数组中
                c[j]=ch[i];
            }else {
//                如果当前字符是空的,那么就填充字符
                c[j--]='0';
                c[j--]='2';
                c[j]='%';
            }
            i--;
        }
        return c;
    }
}

时间复杂度:O(N)

3.3,思路三

先求出题目中字符串的长度,然后对字符串中逐个字符进行判断,如果遇到空字符,就在新的字符串末尾加%20,直到遍历完整个字符串,然后把新的字符串赋值给s串即可。

  • 代码实现
class Solution {
    public String replaceSpace(String s) {
       int len=s.length();
       String str=s;
       s="";
       for(int i=0;i<len;i++){
           if(str.charAt(i)== ' '){
               s=s.concat("%20");
           }else{
               s=s.concat(str.charAt(i)+"");
           }
       }
        return s;
    }
}

时间复杂度:o(n)

3.4,思路四

  • 可以先把字符串对象转换为一个字符数组,然后对数组中的字符逐个遍历,使用StringBuilder来接受新的字符串对象,每次遇到空格时,就追加%20即可。
  • 代码实现
class Solution {
    public String replaceSpace(String s) {
        char array[]=s.toCharArray();//字符串转换字符数组方法
        //stringBuilder每一次都在原始字符串上追加,不会创建新的字符串
        StringBuilder stringBuilder=new StringBuilder();
        for (char ch:array){//增强的for循环
            if(ch == ' ')
            {
                stringBuilder.append("%20");
            }else {
                stringBuilder.append(ch);
            }
        }
        s=stringBuilder.toString();
        return s;
    }
}
  • 时间复杂度:O(n);

4,从尾到头打印链表

  • 题目描述

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

4.1,思路一

思路一是我们重构链表,也就是我们采用头插法重新建立链表,这样只需要遍历一遍链表即可,但是这样也有缺点,就是我们链表原有的结构已经被改变。

  • 代码实现
vector<int> reversePrint(ListNode* head) {
    vector<int> res;
    ListNode* pre = NULL;
    ListNode* cur = head;
    while(cur != nullptr){
        ListNode* next = cur->next;
        
        cur->next = pre;
        pre = cur;
        cur = next;
    }

    while(pre){
        res.push_back(pre->val);
        pre = pre->next;
    }

    return res;

}
  • 时间复杂度:o(n)

4.2,思路二

逆序打印链表,很明显是符合栈的存储方式,所以我们也可以借助栈结构进行遍历。

  • 代码实现
class Solution {
    public int[] reversePrint(ListNode head) {
        Stack<ListNode> stack = new Stack<ListNode>();
        ListNode temp = head;
        while (temp != null) {
            stack.push(temp);
            temp = temp.next;
        }
        int size = stack.size();
        int[] print = new int[size];
        for (int i = 0; i < size; i++) {
            print[i] = stack.pop().val;
        }
        return print;
    }
}
  • 时间复杂度:O(N)

4.3,思路三

如果我们知道了链表中节点的个数,那么我们的数组容量大小也就确定了,所以只需要从头到尾逐个遍历链表即可,然后把遍历的元素从数组的后面向前放即可。

  • 代码实现
class Solution {
    public static int[] reversePrint(ListNode head) {
        ListNode node = head;
        int count = 0;
        while (node != null) {
            ++count;
            node = node.next;
        }
        int[] nums = new int[count];
        node = head;
        for (int i = count - 1; i >= 0; --i) {
            nums[i] = node.val;
            node = node.next;
        }
        return nums;
    }
}
  • 时间复杂度:O(n)

持续更新…

欢迎关注作者个人公众号,专注分享java,大数据,python,以及计算机基础知识。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值