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.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 翻转字符串。
题目一概述:翻转单词顺序
思路:分两次翻转,第一次翻转整个字符,第二次翻转每个单词即可。
题目二概述:左旋转字符串
思路:把字符串从需要翻转的地方分两部分,各自翻转后再整体翻转。