剑指offer-------数组、字符串

剑指offer 思路总结

1、数组

1.1  数组中重复的数字

    题目概要:在一个长度为n的数组里所有的数字都在0-n-1的范围内。数组中某些数字是重复的。

(1)若是要找出任意一个重复数字

          思路:对数组进行一次遍历,当扫描到下标为i的数字m时,首先比较这个数字m是不是等于i,

                     如果是,则继续扫描下一个数字;

                     如果不是,则判断它和A[m]是否相等,如果是,则找到了第一个重复的数字(在下标为i和m的位置都出现了m);                       如果不是,则把A[i]和A[m]交换,即把m放回属于它的位置;

                     重复上述过程,直至找到一个重复的数字;

                     时间复杂度:O(n),空间复杂度:O(1)

(2)不修改数组找出重复的数字

          思路:对数字范围进行二分查找,然后遍历数组,重新确定重复数字的范围。

                     重复上述过程,直至找到重复的数字。

(3)找出所有的重复数字

          思路:可以考虑遍历一次数组,然后把对应的下标的数字进行相反数的变换,如果对应下标的数字为负数,则找到一个重复数字,继续遍历重复上述操作即可。

 

1.2 二维数组中的查找

     题目概述:一个二维数组中,每一行从左到右递增,每一列从上到下递增。判断数组中是否存在某个数字。

          思路可以考虑从数组的左下角或者右上角进行一一排查,例如右上角开始的算法,

                    如果nums[row][col] > target , col--;

                    如果nums[row][col] < target , row ++;

                    否则,返回true.

 

1.3 旋转数组的最小数字

      题目概述:输入一个递增数组的旋转,找出旋转数组的最小值。

          思路:可以采用一个二分查找法,每次取中间的数字跟左指针进行比较,根据比较的结果进行指针的调整。若遇到相同的                       情况,就采用顺序查找法。

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 j--;   //采用顺序
        }
        return numbers[i];
    }
}

1.4 调整数组,使奇数位于偶数前面

     思路:双指针法。扩展,把条件封装成函数,可以实现代码的重用性。

 

1.5 数组中出现次数超过一半的数字

     题目概述:给定一个数组,其中的数字有一个出现次数超过一半,求这个数字。

     思路:(1)基于Partiton法,可以说是和快排一样的思想。选一个数字为基准,然后分成两个部分。(前提是可以修改数组)

                 (2)若不可修改数组,可以采用一次遍历法,相反消减法。

class Solution {
    public int majorityElement(int[] nums) {
        int count = 1;
        int temp = nums[0];
        for(int i = 1; i < nums.length; ++i){
            if(temp != nums[i]){
                count--;
                if(count == 0){
                    temp = nums[i];
                    count = 1;
                }
            }else{
                count++;
            }
        }
        return temp;
    }
}

1.6 最小的k个数

     题目概述:输入一个整数数组,找出其中最小的k个数。

     思路:(1)当数组数量不大,且可以修改数组时可以采用跟快排中的二分法。

                (2)当数量很大时,采用堆排序可以更快。


public class GetLeastNumbers {
    public ArrayList getLeast(int[] nums , int k){
        ArrayList res = new ArrayList<>();
        if(nums == null || nums.length == 0 || nums.length < k || k == 0)
            return res;
        //构建大顶堆
        PriorityQueue heap = new PriorityQueue<>(k, new Comparator() {
            @Override
            public int compare(Integer integer, Integer t1) {
                return t1 - integer;
            }
        }) ;
        for(int i = 0; i < nums.length; ++i){
            if(heap.size() < k){
                heap.add(nums[i]);
            }else{
                if(heap.peek() > nums[i]){
                    heap.poll();
                    heap.add(nums[i]);
                }
            }
        }
        for(Integer num : heap){
            res.add(num);
        }
        return res;
    }
}

1.7 连续子数组中的最大和

     题目概述:给定一个整数数组,有正数也有负数。求连续子树组中的最大和。

     思路:可以通过找规律得出方法。

class Solution {
    public int maxSubArray(int[] nums) {
        if(nums == null || nums.length == 0)
             throw new RuntimeException("输入数组不能为空!");
        int max = nums[0];
        int res = nums[0];
        for(int i = 1; i < nums.length; ++i){
            max = Math.max(max + nums[i] , nums[i]);
            res = Math.max(res , max);
        }     
        return res;
    }
}

1.8 把数组排成最小的数

      题目概述:输入一个整整数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

      思路:利用String类的字符排序。(注意数组的范围)


public class PrintMinNum {
    public String minNum(int[] nums){
        if(nums == null || nums.length == 0)
            return "";
        String[] strings = new String[nums.length];
        for(int i = 0; i < nums.length; ++i){
            strings[i] = String.valueOf(nums[i]);
        }
        Arrays.sort(strings , new Comparator(){  //对String进行排序
            @Override
            public int compare(String s, String t1) {
                return (s + t1).compareTo(t1 + s);
            }
        });
        StringBuilder sb = new StringBuilder();
        for(String str : strings){
            sb.append(str);
        }
        return sb.toString();
    }
}

1.9 礼物的最大价值

      题目概述:给定一个m * n 的数组,每个礼物都有一定的价值,从棋盘左上角到右下角,求礼物最大价值。

      思路可以采用动态规划的方法

class Solution {
    public int maxValue(int[][] grid) {
        if(grid == null || grid.length == 0)
             return 0;
        int row = grid.length;
        int col = grid[0].length;
        int temp[] = new int[col];  //设置辅助数组
        for(int i = 0; i < row; ++i){   
            for(int j = 0; j < col; ++j){ 
               int left = 0;     
               int up = 0;
               if(i > 0)     
                  up = temp[j];
               if(j > 0)
                  left = temp[j - 1];
               temp[j] = Math.max(up , left) + grid[i][j];      
            }
        }     
        return temp[col - 1];
    }
}

1.10 数组中的逆序对

       题目概述:在数组中的两个数字,如果前面一个数字大于后面的数字,则两个数字组成一个逆序对。给定一个数组,求其中的逆序对。

       思路:可以考虑利用归并排序的思想,分而治之。

package fifth.面51;

public class InversePairs {
    public int pairsCount(int[] nums){
        if(nums == null || nums.length == 0){
            return 0;
        }
        int[] copyNum = new int[nums.length];
        return helper(nums , copyNum , 0 , nums.length - 1);
    }

    private int helper(int[] nums, int[] copyNum, int start , int end) {
        if(start == end){  //只有一个值,直接返回1
            return 0;
        }
        int mid = (end + start) >> 1;   //取中值
        int leftcount = helper(nums , copyNum , start , mid);  //左边数组的逆序对数
        int rightcount = helper(nums , copyNum , mid + 1 , end);//右边数组的逆序对数
        
        int leftIndex = mid;   //左边数组最右索引
        int rightIndex = end;  //右边数组最右索引
        int count = 0;         //逆序对数
        int copyIndex = end;   //填充到数组的索引
        while (leftIndex >= start && rightIndex >= mid + 1){  //合并时计算逆序对数
            //若左子数组的值比右子树的值大,则进行计算
            if(nums[leftIndex] > nums[rightIndex]){
                count += rightIndex - mid;
                copyNum[copyIndex--] = nums[leftIndex--];
            }else{
                copyNum[copyIndex--] = nums[rightIndex--];
            }
        }
        for(; leftIndex >= start; --leftIndex){
            copyNum[copyIndex--] = nums[leftIndex];
        }
        for(; rightIndex > mid; --rightIndex){
            copyNum[copyIndex--] = nums[rightIndex];
        }
        for(int i = start; i <= end; ++i){
            nums[i] = copyNum[i];
        }
        return count + leftcount + rightcount;
    }
}

1.11 在排序数组中查找数字

       (1)题目一概述:数字在排序数组中出现的次数。

       思路:采用二分法查找,分别找到数字最左和最右的数字,然后相减即可。可以控制时间为logn。

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;
    }
}

       (2)题目二概述:0-n-1中缺失的数字(数字值缺失一个)

        思路:采用二分法。借助每个元素与其对应的下标的关系。

1.12 卖股票的最佳时机

      (1)题目一概述:给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

         思路:可以采用一次遍历的方法。使用一个变量记录最小的价格,然后一次遍历求最大利益。

public class Solution {
    public int maxProfit(int prices[]) {
        int minprice = Integer.MAX_VALUE;  //最小价格
        int maxprofit = 0;                 //最大利益
        for (int i = 0; i < prices.length; i++) {
            if (prices[i] < minprice)
                minprice = prices[i];      //如果当前价格比之前的小,替换
            else if (prices[i] - minprice > maxprofit) 
                maxprofit = prices[i] - minprice;  //更新最大利益情况
        }
        return maxprofit;
    }
}

      (2)题目概述:条件可以尽可能地完成更多的交易(多次买卖一支股票),但买之前,一定是先把股票卖了。

        思路:一次遍历法(峰谷计算),只要后者比前者大,就是获利。

class Solution {
    public int maxProfit(int[] prices) {
        int maxprofit = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] > prices[i - 1])      //如果后者比前者大,就是可以获利,加上来
                maxprofit += prices[i] - prices[i - 1];
        }
        return maxprofit;
    }
}

     (3)题目概述:最多只能完成两笔交易。

        思路:采用状态方程法。详解链接:leetCode买股票问题状态方程全搞定

基础条件:
dp[-1][k][0] = dp[i][0][0] = 0
dp[-1][k][1] = dp[i][0][1] = -infinity

状态转移方程:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

我们可以用自然语言描述出每一个状态的含义,比如说 dp[3][2][1] 的含义就是:
今天是第三天,我现在手上持有着股票,至今最多进行 2 次交易。再比如
 dp[2][3][0] 的含义:今天是第二天,我现在手上没有持有股票,
至今最多进行 3 次交易。

 

1.13数组中数字出现的次数

     题目概述:一个整数数组里除了两个数字外,其他数字都出现了两次。请找出这两个数字。时间复杂度O(n),空间复杂度是O(1)

     思路:采用位移运算法。就本题,两个相同的数异或为0,然后根据两个不相同的数的异或为1的位进行分组,每组在异或。

 

 2、字符串

2.1 替换空格

      题目概述:把字符串的每一个空格替换成“XXX”。

      思路:先遍历字符串,统计空格的总数,然后对字符串进行矿容,再利用双指针重写即可。

 

2.2 字符串的排列

      题目概述:输入一个字符串,打印出该字符串的所有排列。如ab, 打印ab,ba。

      思路:可以采用递归法。就是将字符串看成两部分来处理。

class Solution {
    List res = new LinkedList<>();
    char[] c;
    public String[] permutation(String s) {
        c = s.toCharArray();
        dfs(0);
        return res.toArray(new String[res.size()]);
    }
    void dfs(int x) {
        if(x == c.length - 1) {
            res.add(String.valueOf(c)); // 添加排列方案
            return;
        }
        HashSet set = new HashSet<>();
        for(int i = x; i < c.length; i++) {
            if(set.contains(c[i])) continue; // 重复,因此剪枝
            set.add(c[i]);
            swap(i, x); // 交换,将 c[i] 固定在第 x 位 
            dfs(x + 1); // 开启固定第 x + 1 位字符
            swap(i, x); // 恢复交换
        }
    }
    void swap(int a, int b) {
        char tmp = c[a];
        c[a] = c[b];
        c[b] = tmp;
    }
}

 

2.3 把数字翻译成字符串

      题目概述:给定一个数字,0-25 分别翻译成a-z;求这个数字有多少种不同的翻译方法。

      思路:动态规划。因为从上到下会产出较多的重复子问题。

class Solution {
    public int translateNum(int num) {
        if(num < 0)
           return 0;
        return getSum(String.valueOf(num));   
    }
    private int getSum(String str){
        int first = 1;
        int second = 1;
        int p = 0;
        for(int i= 1; i < str.length(); ++i){
           int temp = Integer.parseInt(String.valueOf(str.charAt(i - 1) + String.valueOf(str.charAt(i))));
           if(temp >= 0 && temp <= 25 && str.charAt(i - 1) != '0'){
               p = 1;
           }else{
               p = 0;
           }
           temp = first * p +second;
           first = second;
           second = temp;
        }
        return second;
    }
}

2.4 最长不含重复字符的字符串

     题目概述:从一个字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

     思路:一次遍历法,可以借助一个26大小辅助数组,然后记录每个字符上次出现的位置。

package fifth.面48;

public class GetLongString {
    public int getStringNum(String str){
        if(str == null || str.length() == 0)
            return 0;
        int curLen = 0;          //当前长度
        int maxLen = 0;          //最大长度
        int[] temp = new int[26];   //创建一个辅助数组
        for(int i = 0; i < 26; ++i){
            temp[i] = -1;           //初始值设为-1
        }
        for(int i = 0; i < str.length(); ++i){
            int preIndex = temp[str.charAt(i) - 'a'];
            if(preIndex < -1 || i - preIndex > curLen){ //判断是否需要的更改curLen
                ++curLen;
            }else{   
                if(curLen > maxLen)
                    maxLen = curLen;
                curLen = i - preIndex;
            }
            temp[str.charAt(i) - 'a'] = i;  //设置当前字符的位置
        }
        if(curLen > maxLen)
            maxLen = curLen;
        return maxLen;
    }
}

 

2.5 翻转字符串。

      题目一概述:翻转单词顺序

      思路:分两次翻转,第一次翻转整个字符,第二次翻转每个单词即可。

 

      题目二概述:左旋转字符串

      思路:把字符串从需要翻转的地方分两部分,各自翻转后再整体翻转。

你可能感兴趣的:(算法,算法,数据结构,leetcode)