文章目录
38. 报数
报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:
1. 1
2. 11
3. 21
4. 1211
5. 111221
1 被读作 “one 1” (“一个一”) , 即 11。
11 被读作 “two 1s” (“两个一”), 即 21。
21 被读作 “one 2”, “one 1” (“一个二” , “一个一”) , 即 1211。
给定一个正整数 n(1 ≤ n ≤ 30),输出报数序列的第 n 项。
注意:整数顺序将表示为一个字符串。
示例 1:
输入: 1
输出: “1”
示例 2:
输入: 4
输出: “1211”
分析:
- 1
- 2 描述的是1,是一个1,也就是11
- 3 描述的是11,是两个1,也就是21
- 4 描述的是21,是一个2一个1,也就是12-11
- 5 描述的是1211, 是一个1,一个2,两个1,也就是11-12-21
- 6 描述的是111221,是三个1,两个2,一个1,也就是31-22-11
- 7 描述的是312211,是一个3一个1两个2两个1,也即是13-11-22-21
- 以此类推
我采用了递归的解法,由于此题限定了n不大于30,其实也可以采用穷举法得出所有可能结果的数组进行求解。
class Solution {
public String countAndSay(int n) {
return recursion(new char[]{'1'},n - 1);
}
public String recursion(char[] res,int index){
if(index == 0){
return new String(res);
}else{
int num = 1;
StringBuilder builder = new StringBuilder();
for(int i = 0;i<res.length;i++){
if(i != res.length-1 && res[i] == res[i+1] ){
num++;
}else{
builder.append(num);
builder.append(res[i]);
num = 1;
}
}
return recursion(builder.toString().toCharArray(),index-1);
}
}
}
39. 组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
- 所有数字(包括 target)都是正整数。
- 解集不能包含重复的组合。
示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
此题关键采用回溯算法解答
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
//先从小到大排序,帮助去重与剪枝优化
Arrays.sort(candidates);
recursion(candidates,target,new ArrayList());
return result;
}
List<List<Integer>> result = new ArrayList<>();
public void recursion(int[] candidates,int target,int index,List<Integer> list){
//如果target比candidates[index]还小了,没必要再进行下去了
while(index < candidates.length && target >= candidates[index]){
if(target == candidates[index]){
//得出满足条件的集合
list.add(candidates[index]);
result.add(list);
return;
}else{
//复制新集合保存结果避免对下一轮递归的影响
List newList = new ArrayList(list);
newList.add(candidates[index]);
recursion(candidates,target - candidates[index],index,newList);
}
index++;
}
}
}
40. 组合总和 II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
- 所有数字(包括目标数)都是正整数。
- 解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]
此题还是采用回溯算法,难点在于要考虑结果集去重
class Solution {
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
recursion(candidates, target, 0, new ArrayList());
return result;
}
public void recursion(int[] candidates, int target, int index, List<Integer> list) {
int start = index;
while (index < candidates.length && target >= candidates[index]) {
//结果集去重
if(index > start && candidates[index] == candidates[index - 1]){
index++;
continue;
}
if (target == candidates[index]) {
list.add(candidates[index]);
result.add(list);
return;
} else {
List newList = new ArrayList(list);
newList.add(candidates[index]);
recursion(candidates, target - candidates[index], index + 1, newList);
}
index++;
}
}
}
42. 接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
每次找出最高的柱子和第二高的柱子,得出它们之间能容纳的雨水,然后通过递归得到左半部和右半部的结果相加就是所有的雨水了。
class Solution {
public int trap(int[] height) {
if(height==null || height.length == 0 || height.length == 1){
return 0;
}
int start = 0;
int end = height.length - 1;
//去掉开头的0
while(height[start] == 0){
start++;
}
//去掉末尾的0
while(height[end] == 0){
end --;
}
if(start +1 >= end){
return 0;
}
return recursion(arraySub(height,start,end));
}
public int recursion(int[] res){
int total = 0;
int start = 0;
int end = res.length - 1;
for(int i = 1; i<res.length -1;i++){
//得到res中最高的两个柱子的坐标
if(res[i] > res[start] || res[i] > res[end]){
if(res[start] > res[end]){
end = i;
}else{
start = i;
}
}
}
//有可能出现end<start的情况
if(start > end){
int tmp = start;
start = end;
end = tmp;
}
//考虑两个柱子相邻的情况,就没必要计算了
if(start + 1 != end){
//取两个柱子中较矮的那根
if(res[start] > res[end]){
total = res[end] * (end - start - 1);
}else{
total = res[start] * (end - start - 1);
}
//减去两根柱子之间的那些柱子的高度就是结果
for(int i = start + 1;i < end;i++){
total -= res[i];
}
}
//在start=1的情况下左子集必然只有两根柱子,所以不用考虑
if(start > 1){
//求左子集的雨水
total += recursion(arraySub(res,0,start));
}
if(end < res.length-2){
//求右子集的雨水
int[] newRes = new int[res.length - end];
System.arraycopy(res,end,newRes,0,newRes.length);
total += recursion(newRes);
}
return total;
}
public static int[] arraySub(int[] data,int start,int end){
int[] C=new int[end-start+1];//新建数组C长度为start-end
for(int i=start,j=0;i<=end;i++,j++){
C[j]=data[i];
}
return C;//返回截取数组的地址
}
}
43. 字符串相乘
给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
示例 1:
输入: num1 = “2”, num2 = “3”
输出: “6”
示例 2:
输入: num1 = “123”, num2 = “456”
输出: “56088”
说明:
- num1 和 num2 的长度小于110。
- num1 和 num2 只包含数字 0-9。
- num1 和 num2 均不以零开头,除非是数字 0 本身。
- 不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。
分析:
num1的第m位(高位从0开始)和num2的第n位相乘的结果必不大于2位,其个位放在[m+n],十位放在 [m+n+1]
例: 123 * 45, 123的第0位 3 和45的第1位 4 乘积 为12 ,其十位1存放在[2],个位放在[1]中
index: 0 1 2 3 4
1 2 3
* 4 5
---------
1 5
1 0
0 5
---------
0 6 1 5
1 2
0 8
0 4
---------
0 5 5 3 5
这样我们就可以单独都对每一位进行相乘计算把结果存入相应的index中
class Solution {
public String multiply(String num1, String num2) {
if(num1.equals("0") || num2.equals("0")){
return "0";
}else{
char[] chars1 = num1.toCharArray();
char[] chars2 = num2.toCharArray();
for(int i = 0;i<chars1.length;i++){
chars1[i] = (char)(chars1[i] - 48);
}
for(int i = 0;i<chars2.length;i++){
chars2[i] = (char)(chars2[i] - 48);
}
int tmp = 0;
//一个n位数和m位数的积必不大于m+n位
int[] result = new int[chars1.length+chars2.length];
for(int i = chars1.length - 1,m=0;i >= 0;i--,m++){
for(int j = chars2.length - 1,n=0; j >=0;j--,n++){
tmp = chars1[i] * chars2[j] + result[m+n];
result[m+n] = tmp % 10;
//满10进1
result[m+n+1] += tmp / 10;
}
}
StringBuilder sb = new StringBuilder();
int index = result.length - 1;
//去除高位的0
while(result[index] == 0){
index--;
}
for(;index >= 0 ;index--){
sb.append(result[index]);
}
return sb.toString();
}
}
}
分苹果
题目:
果园里有一堆苹果,一共n头(n小于9且大于1)熊来分,第一头将苹果分为n份,多出一个扔掉,拿走其中一份,接着第二天熊重复这个过程,即先均分n份,丢掉多出的一个,然后拿走一份,依次类推直到最后一头熊都是这样(最后一头熊可以拿走0个也算均分)。问最初这堆苹果最少有多少个。
public class AppleAlgorithm {
private int n;
public AppleAlgorithm(int n) throws Exception {
if(n<2||n>8){
throw new Exception("非法的个数");
}
this.n = n;
}
public int getN(){
return n;
}
public boolean splitApple(int num, int apple) {
if (num == 0) {
return true;
}else if(apple <= 0){
return false;
}
if ((apple-1) % n == 0) {
return splitApple(num - 1, (apple - 1) / n * (n - 1));
} else {
return false;
}
}
public static void main(String[] args) throws Exception {
int i = 1;
AppleAlgorithm apple = new AppleAlgorithm(2);
while (true) {
if (apple.splitApple(apple.getN(), i)) {
System.out.println("result = " + i);
break;
}
i++;
}
}
}
打印n次方
如题:输入正整数n,打印出1到10的n次方的值,如输入3,则输出1,2,3…,999.
看上去是一道很简单的题目,只要有点编程基础,我想大部分人都能写出如下代码:
@Test
public void test5() {
printNumber(3);
}
public void printNumber(int n) {
int num = 1;
while (n-- > 0) {
num *= 10;
}
for (int i = 1; i < num; i++) {
System.out.print(i+" ");
if (i % 10 == 0)
System.out.println();
}
}
但是上述代码有个很隐晦的bug,那就是当n取值很大时,比如100,那么10^n这个值也就很大,明显会超过int最大值,就算换成long也依然会出问题,所以完美解法提供如下:
@Test
public void test4() {
printNumber(10);
}
//这个值记录输出的值的个数
int m = 0;
public void pirntNumber(int n){
int[] nums = new int[n];
printNum(nums, n);
System.out.println(" \n m = " + m);
}
//原理很简单,说白了就是递归而已。
public void printNum(int[] nums, int n) {
//要输出多少位的数值,就递归多少次
if (n > 0) {
n--;
//数组由后往前依次存放要输出的数字的当前位数的值(从高到低)
for (int i = 0; i < 10; i++) {
nums[n] = i;
printNum(nums, n);
}
} else {
//当递归次数用尽,那么就开始打印这个数字,从高位到低位开始
int k = nums.length - 1;
/*下面这个循环是为了去掉无效的0,比如数组中依次存放的是应该是0,8,0,0,那么输出结果就应该是80.
*/
while (nums[k] == 0) {
k--;
if (k < 0) {
return;
}
}
//每输出10个数换行
if (m++ % 10 == 0) {
System.out.println();
}
//从后往前输出数组中的数字,也就是输出10^n的值
for (; k >= 0; k--) {
System.out.print(nums[k]);
}
System.out.print(" ");
return;
}
}
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:“23”
输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
电话号码的字母组合
这道题本来想用循环去做,但是发现循环的个数不固定,因此这种情况得用递归
class Solution {
//map存放数字对应的字符集
Map<String, String> phone = new HashMap<String, String>() {{
put("2", "abc");
put("3", "def");
put("4", "ghi");
put("5", "jkl");
put("6", "mno");
put("7", "pqrs");
put("8", "tuv");
put("9", "wxyz");
}};
public List<String> letterCombinations(String digits) {
List<String> list = new ArrayList<>();
combination("",0,digits,list);
return list;
}
//递归方法,result是每条组合结果,num是digits的下表,digits是你输入的数字字符串,list是结果列表
public void combination(String result,int num,String digits,List list){
if(num < digits.length()){
//获取数字对应的字符计划,注意这里要加上"",否则会有空指针异常,因为charAt()方法返回的是char类型,然后结果转为char[]数组
char[] cs = phone.get(digits.charAt(num++)+"").toCharArray();
for(char c : cs){
//组合每个数字所有字符组合
combination(result+c,num,digits,list);
}
}else{
//考虑空字符串情况下不保存
if(!"".equals(result)){
list.add(result);
}
}
}
}
快乐数
编写一个算法来判断一个数是不是“快乐数”。
一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。
示例:
输入: 19
输出: true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
此题的关键在于判断无限循环的情况,只要能证明某一个结果已经出现过,那么就必存在无限循环
class Solution {
public boolean isHappy(int n) {
Set<Integer> set = new HashSet<>();
int h = recursion(n);
set.add(h);
while(h != 1){
h = recursion(h);
//根据判断是否存在重复值,若存在则结束循环
if(set.contains(h)){
return false;
}else{
set.add(h);
}
}
return true;
}
public int recursion(int n) {
if(n / 10 == 0){
//n为个数时返回n的平方
return n*n;
}else{
//取n的余数
int mod = n%10;
return mod*mod + recursion(n/10);
}
}
}
求最短路径
给定一个二维数组,求任意两点直接最少经过的点数。
public static int findPath(int[][] rout, boolean[] visited, int start,
int end, int dist, int minDist) {
visited[start - 1] = true;
for (int i = 1; i < rout[start].length; i++) {
dist += rout[start][i-1];
if (i != start && !visited[i-1]) {
if (i == end) {
if (dist < minDist || minDist == 0){
minDist = dist;
}
}else if(dist < minDist || minDist == 0){
minDist = findPath(rout,visited,i,end,dist,minDist);
}
}
dist -= rout[start][i-1];
}
visited[start-1] = false;
return minDist;
}
- rout数组下标第一维从1开始,代表点的坐标,下标为0我预留记录路线图用,第二维代表点与点之间的距离,值为0代表不可达会直接跳过后面的寻址,visited数组用来标记哪些坐标被访问过,防止陷入无限递归中,start代表起点,end代表终点,dist记录当前寻址过的路线长度,minDist记录起点到终点最小路线长度。
- 核心思想无非两点:1.穷举,2.递归。我在此处有个优化的地方在于当我当前寻址过的路线已经大于已经得出的最小路线长度时候就会跳过这次搜寻过程。若结果minDist=0 那么代表起点无法到达终点。
输入一串数字,找到其中两个数的和为某个给定值的坐标
例子:输入一串数字如 2,8,6,7,3;找到其中两个数的和加起来等于9的坐标;显然2+7=8,那么结果为0,3
解法一:
int result = 9;
public void findIndex(int... arr){
for(int i = 0;i<arr.length;i++){
for(int j=i;j<arr.length;j++){
if(arr[i] + arr[j] == result){
System.out.println(i);
System.out.println(j);
return;
}
}
}
}
显然上述代码在最坏的遍历情况下,时间复杂度接近于O(n2)了,这个肯定不是最好的解法。我们不妨换位思考一下,既然是求2+7=9的情况,那么是不是也可以换做求9-2=7的情况呢。只要当我们遍历一个数num的时候,只要知道前面是否存在一个数9-num的值不就可以了吗,很显然,map最适合帮忙搞定了。这样即使最坏的情况只要遍历一遍这串数字就可以得出结果了,时间复杂度不大于O(n)。
解法二:
int result = 9;
public void findIndex(int... arr){
Map<Integer,Integer> map = new HashMap();
for(int i = 0;i<arr.length;i++){
map.put(arr[i],i);
if(map.get(result - arr[i])!= null){
System.out.println(map.get(result - arr[i]));
System.out.println(i);
return;
}
}
}
奇数值单元格的数目
给你一个 n 行 m 列的矩阵,最开始的时候,每个单元格中的值都是 0。
另有一个索引数组 indices,indices[i] = [ri, ci] 中的 ri 和 ci 分别表示指定的行和列(从 0 开始编号)。
你需要将每对 [ri, ci] 指定的行和列上的所有单元格的值加 1。
请你在执行完所有 indices 指定的增量操作后,返回矩阵中 「奇数值单元格」 的数目。
示例 1:
输入:n = 2, m = 3, indices = [[0,1],[1,1]]
输出:6
解释:最开始的矩阵是 [[0,0,0],[0,0,0]]。
第一次增量操作后得到 [[1,2,1],[0,1,0]]。
最后的矩阵是 [[1,3,1],[1,3,1]],里面有 6 个奇数。
示例 2:
输入:n = 2, m = 2, indices = [[1,1],[0,0]]
输出:0
解释:最后的矩阵是 [[2,2],[2,2]],里面没有奇数。
提示:
- 1 <= n <= 50
- 1 <= m <= 50
- 1 <= indices.length <= 100
- 0 <= indices[i][0] < n
- 0 <= indices[i][1] < m
class Solution {
public int oddCells(int n, int m, int[][] indices) {
//初始化矩阵
int[][] mat = new int[n][m];
for(int i = 0;i<indices.length;i++){
int[] in = indices[i];
//指定行单元格数目加1
for(int k = 0;k<m;k++){
mat[in[0]][k] += 1;
}
//指定列单元格数目加1
for(int j = 0;j<n;j++){
mat[j][in[1]] += 1;
}
}
int num = 0;
//遍历结果矩阵找到奇数
for(int i = 0;i<n;i++){
for(int j = 0;j<m;j++){
//注意这里和mat[i][j] % 2 != 0并不等价哦
if(mat[i][j] % 2 == 1){
num++;
}
}
}
return num;
}
}
重构 2 行二进制矩阵
给你一个 2 行 n 列的二进制数组:
矩阵是一个二进制矩阵,这意味着矩阵中的每个元素不是 0 就是 1。
第 0 行的元素之和为 upper。
第 1 行的元素之和为 lower。
第 i 列(从 0 开始编号)的元素之和为 colsum[i],colsum 是一个长度为 n 的整数数组。
你需要利用 upper,lower 和 colsum 来重构这个矩阵,并以二维整数数组的形式返回它。
如果有多个不同的答案,那么任意一个都可以通过本题。
如果不存在符合要求的答案,就请返回一个空的二维数组。
示例 1:
输入:upper = 2, lower = 1, colsum = [1,1,1]
输出:[[1,1,0],[0,0,1]]
解释:[[1,0,1],[0,1,0]] 和 [[0,1,1],[1,0,0]] 也是正确答案。
示例 2:
输入:upper = 2, lower = 3, colsum = [2,2,1,1] 输出:[]
示例 3:
输入:upper = 5, lower = 5, colsum = [2,1,2,0,1,0,1,2,0,1]
输出:[[1,1,1,0,1,0,0,1,0,0],[1,0,1,0,0,0,1,1,0,1]]
提示:
- 1 <= colsum.length <= 10^5
- 0 <= upper, lower <= colsum.length
- 0 <= colsum[i] <= 2
题目分析
- 本题使用 DFS 求解,有 TLE 的可能
- 本题应使用贪心算法,大致思路为:
- 若 colsum[i]=2,则 一定上下均为 1
- 若 colsum[i]=0,则 一定上下均为 0
- 若 colsum[i]=1,则 上下一个 1 一个 0
- 唯一需要讨论的只有 colsum[i]=1的情形,我们可以规定:默认先分配上为 1,再分配下为 1,当upper < lower时,则先分配下为1,再分配上为1
无解的情形与解决方案:- 行元素之和(upper+lower)与列元素之和(∑colsum)不相等
- 解决方案:一次循环,求出 ∑colsum,与 upper+lower 比较行元素之和
- 解决方案 1:排除所有 00 的项与 22 的项,观察此时 upper 与 lower 是否为负值
- 解决方案 2:直接分配,分配完成后,观察此时 upper 与 lower 是否为负值
class Solution {
List<List<Integer>> list = new ArrayList<>();
public List<List<Integer>> reconstructMatrix(int upper, int lower, int[] colsum) {
int sum = 0;
for (int col : colsum) {
sum += col;
}
if (upper + lower != sum) {
return list;
}
recursion(upper, lower, colsum, new Integer[2][colsum.length], 0);
return list;
}
public void recursion(int upper, int lower, int[] colunm, Integer[][] res, int n) {
if (n == colunm.length) {
if (upper == 0 && lower == 0) {
List list1 = new ArrayList(n);
List list2 = new ArrayList(n);
Collections.addAll(list1, res[0]);
Collections.addAll(list2, res[1]);
list.add(list1);
list.add(list2);
}
return;
}
if (colunm[n] == 0) {
res[0][n] = 0;
res[1][n] = 0;
recursion(upper, lower, colunm, res, n + 1);
if (list.size() > 0) {
return;
}
} else if (colunm[n] == 2) {
if (upper < 1 || lower < 1) {
return;
}
res[0][n] = 1;
res[1][n] = 1;
recursion(upper - 1, lower - 1, colunm, res, n + 1);
if (list.size() > 0) {
return;
}
} else {
if (upper > lower) {
if (upper > 0) {
res[0][n] = 1;
res[1][n] = 0;
recursion(upper - 1, lower, colunm, res, n + 1);
if (list.size() > 0) {
return;
}
} else if (lower > 0) {
res[0][n] = 0;
res[1][n] = 1;
recursion(upper, lower - 1, colunm, res, n + 1);
if (list.size() > 0) {
return;
}
}
} else {
if (lower > 0) {
res[0][n] = 0;
res[1][n] = 1;
recursion(upper, lower - 1, colunm, res, n + 1);
if (list.size() > 0) {
return;
}
} else if (upper > 0) {
res[0][n] = 1;
res[1][n] = 0;
recursion(upper - 1, lower, colunm, res, n + 1);
if (list.size() > 0) {
return;
}
}
}
}
}
}