3.18
回溯算法
组合总和III
题目:找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 – 9 的正整数,并且每种组合中不存在重复的数字。
在[1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合。
LinkedList
LinkedList
是 Java 标准库中的一个集合类,它实现了List
接口。LinkedList
使用双向链表的数据结构来存储元素,因此,与ArrayList
相比,它在列表中插入或删除元素时通常能提供更好的性能,特别是在列表的开头或中间进行操作时。在回溯算法中,使用
LinkedList
作为路径(path
)存储的数据结构是一个常见的选择,因为它经常需要在列表的末尾添加元素或删除最后的元素,而这些操作LinkedList
都能高效地完成。以下是
LinkedList
的一些重要特点和常用方法:特点
动态大小:
LinkedList
可以根据需要自动扩展和缩减其大小。元素类型:
LinkedList
中只能存储Integer
类型的对象,不能存储基本数据类型int
。在存取基本类型int
时,自动装箱和拆箱会自动发生。双向遍历:由于是双向链表,可以从两个方向遍历元素。
非同步:
LinkedList
是非同步的,所以如果在多线程环境中使用,需要外部同步。常用方法
add(E e)
: 将指定元素添加到列表的末尾。
add(int index, E element)
: 在列表的指定位置插入元素。
addFirst(E e)
和addLast(E e)
: 分别在列表开头和末尾添加元素。
get(int index)
: 返回列表中指定位置的元素。
remove(int index)
: 删除列表中指定位置的元素。
removeFirst()
和removeLast()
: 删除并返回列表的第一个或最后一个元素。
size()
: 返回列表中的元素数。
isEmpty()
: 如果列表不包含元素,返回true
。在回溯算法中的使用
在回溯算法中,
LinkedList
作为path
的数据结构使用,具体来说,有以下几点优势:
快速的插入和删除操作:
path.addLast(i)
和path.removeLast()
能够快速地在路径的末尾添加或移除元素,这对于回溯算法中尝试和回退是非常有用的。不需要动态数组的随机访问功能:在大多数回溯算法实现中,我们并不需要随机访问路径中的元素,所以
LinkedList
的缺点(比如相对较慢的随机访问性能)在这里并不突出。自动装箱和拆箱:由于
LinkedList
存储的是Integer
对象,所以当我们添加一个基本类型int
时,它会自动被装箱为Integer
对象,同样地,在获取时会自动拆箱回int
类型。使用
LinkedList
的一个小缺点是每个元素都需要额外的内存空间来存储前驱和后继指针,但在回溯算法的上下文中,这通常不是一个大问题,因为算法的空间复杂度主要由递归深度决定。
class Solution { // 存储所有可能的组合结果 List> res = new ArrayList(); // 用于记录当前的组合路径 LinkedList path = new LinkedList(); // 公共方法,开始组合总和的搜索 public List> combinationSum3(int k, int n) { // 从数字1开始,尝试所有可能的组合 backTracking(n, k, 1, 0); // 返回所有有效的组合 return res; } /** * 使用回溯法搜索所有可能的组合 * * @param n 目标数字,组合中数字的总和 * @param k 组合中所需的数字个数 * @param startIndex 开始的索引,避免重复的组合 * @param sum 当前路径中所有数字的总和 */ private void backTracking(int n, int k, int startIndex, int sum){ // 如果当前路径的总和已经大于目标数字n,则没有必要继续搜索 if(sum > n) return; // 检查当前路径的长度是否等于k if(path.size() == k){ // 如果当前路径的数字总和等于n,那么这是一个有效的组合 if(sum == n) res.add(new ArrayList(path)); // 返回上一层递归 return; 服务器托管网 } // 遍历所有可能的选择 // 减少循环的次数,只考虑那些有可能成为解的数字 for(int i = startIndex; i剪枝的另一种写法
// 上面剪枝 i k) return; 执行效率上是一样的 class Solution { LinkedList path = new LinkedList(); List> ans = new ArrayList(); public List> combinationSum3(int k, i服务器托管网nt n) { build(k, n, 1, 0); return ans; } private void build(int k, int n, int startIndex, int sum) { if (sum > n) return; if (path.size() > k) return; if (sum == n && path.size() == k) { ans.add(new ArrayList(path)); return; } for(int i = startIndex; i电话号码的字母组合
题目:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下。注意 1 不对应任何字母。
"23",抽象为树形结构:
取字符串数组中的字符串
String str = numString[digits.charAt(num) - '0'];这行代码的作用是:
digits.charAt(num)
: 获取digits
字符串中位置为num
的字符(数字)。假设num
为0,那么digits.charAt(num)
将会是字符'2'。
- '0'
: 将得到的字符(例如'2')转换为对应的整数(例如2)。在ASCII码表中,数字字符'0'到'9'是连续排列的,'0'的ASCII码是48,'2'的ASCII码是50。因此,'2' - '0'
的结果是2,这是因为50 - 48 = 2
。
numString[digits.charAt(num) - '0']
: 根据步骤2得到的索引从numString
数组中取出对应的字符串。如果得到的索引是2,那么numString[2]
就是"abc"。class Solution { // 使用list来存储所有可能的字母组合 List list = new ArrayList(); public List letterCombinations(String digits) { // 数字到字母的映射,index 0 和 1 是空字符串,因为它们在电话键盘上没有对应的字母 String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; // 如果输入的字符串为空,返回空列表 if (digits == null || digits.length() == 0) return list; // 开始回溯搜索 backtrack(digits, numString, 0); return list; } // 用于构建当前探索路径的字母组合 StringBuilder temp = new StringBuilder(); private void backtrack(String digits, String[] numString, int num) { // 如果当前路径的长度与输入字符串长度相同,说明找到了一个完整的字母组合 if (num == digits.length()) { list.add(temp.toString()); return; } // 获取当前数字对应的所有可能的字母 String str = numString[digits.charAt(num) - '0']; // 遍历这些字母 for (int i = 0; i
-
核心思路
-
初始化: 创建一个全局列表
list
来存储最终的所有字母组合,和一个StringBuilder
temp
来构建每一种可能的字母组合。 -
检查输入: 在
letterCombinations
方法中,首先检查输入的字符串digits
是否为空,如果是,则直接返回空列表。 -
开始回溯: 使用
backtrack
方法进行深度优先搜索,生成所有可能的字母组合。 -
回溯逻辑:
-
如果当前的路径长度(即
temp
的长度)等于输入数字字符串的长度,说明已经形成了一个完整的字母组合,将其添加到list
中。 -
从数字到字母的映射中获取当前数字对应的所有可能字母,遍历这些字母,对每个字母执行以下操作:
-
将其添加到
temp
中。 -
递归地调用
backtrack
,以当前字母为起点,探索下一个数字对应的所有可能字母。 -
在递归返回后,执行回溯操作,即从
temp
中移除最后添加的字母,以尝试其他可能的字母。
-
-
这种方式通过递归和回溯结合,能够高效地搜索出所有可能的字母组合。
-
-
backtrack(digits, numString, 0);
-
String str = numString[digits.charAt(num) - '0'];
-
temp.append(str.charAt(i));
// 将当前字母加入到路径中 appedbacktrack(digits, numString, num + 1); // 继续探索下一个数字
temp.deleteCharAt(temp.length() - 1);
// 回溯,移除路径中的最后一个字母,尝试下一个可能的字母 deleteCharAt
输入输出
(0条未读通知) 牛客竞赛ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛牛客竞赛OJ (nowcoder.com)
字符串排序
import java.util.Scanner; public class Main { public static void main(String[] args){ Scanner in = new Scanner(System.in); while (in.hasNextLine()){ String[] lines = in.nextLine().split(","); int left = 0; int right = lines.length - 1; fastsort(lines , left , right); String outstr = String.join(",",lines); System.out.println(outstr); } } private static void fastsort(String[] lines, int left, int right){ if(left数据范围
-
题目描述中提到的数据范围是
0 ,这个范围超出了Java中
int
类型的最大值2,147,483,647
。因此,你应该使用long
类型来存储输入的整数和它们的和,以避免数值溢出。 -
使用
long
类型,其范围大约在-9.22E18
到9.22E18
之间,足以容纳题目描述中的数据范围0 。
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); while(in.hasNextLong()){ // 使用hasNextLong检查下一个输入项是否可以作为long类型读取 long a = in.nextLong(); // 读取一个长整型数字 long b = in.nextLong(); // 读取另一个长整型数字 System.out.println(a + b); // 输出两个长整型数字的和 } } }
华为机试【入门】
HJ5 进制转换
-
if(str.startsWith("0x") || str.startsWith("0X")) str = str.substring(2);
去掉16进制开头的0x
0X
-
//将字母转换为数值 if(tc>='0' && tc='A' && tc='a' && tc
-
// 获取当前字符 char c = str.charAt(i); // 使用Character.digit方法将十六进制字符转换为对应的十进制数值 // 第二个参数16表示我们正在处理的是十六进制数 int n = Character.digit(c , 16);
import java.util.Scanner; import java.lang.Math; public class Main{ public static void main(String[] args){ Scanner in = new Scanner(System.in); while(in.hasNextLine()){ String s = in.nextLine(); //读入数字 int count = 0; //记录转换后的数字 for(int i=0; i ='0' && tc='A' && tc='a' && tcpackage hw; /** * 描述 * 写出一个程序,接受一个十六进制的数,输出该数值的十进制表示。 * 输入描述: * 输入一个十六进制的数值字符串。 * 输出描述: * 输出该数值的十进制字符串。不同组的测试用例用n隔开。 */ import java.util.Scanner; public class HJ5_进制转换 { public static void main(String[] args) { // 创建一个Scanner对象来读取标准输入 Scanner in = new Scanner(System.in); // 使用一个循环来不断读取输入,直到没有新的输入 while(in.hasNextLine()){ // 读取一行输入 String str = in.nextLine(); // 如果输入的十六进制数以"0x"或"0X"开头,去掉这个前缀 // 因为这个前缀是十六进制的标准表示法,但对于转换来说是不必要的 if(str.startsWith("0x") || str.startsWith("0X")) str = str.substring(2); // 获取去掉前缀后的字符串长度 int length = str.length(); // 初始化一个计数器,代表当前位的数值应该乘以16的多少次方 int count = 1; // 初始化和为0 int sum = 0; // 从字符串的最后一个字符开始遍历,即从十六进制数的最低位开始 for(int i = length - 1; i >= 0 ; i--){ // 获取当前字符 char c = str.charAt(i); // 使用Character.digit方法将十六进制字符转换为对应的十进制数值 // 第二个参数16表示我们正在处理的是十六进制数 int n = Character.digit(c , 16); /** 或者写成 int n = 0; if(c>='0' && c='A' && c='a' && cNC61
NC61:两数之和_牛客博客 (nowcoder.net)
LCR 007. 三数之和
题目:给定一个包含
n
个整数的数组nums
,判断nums
中是否存在三个元素a
,b
,c
,使得a + b + c = 0
?请找出所有和为0
且 不重复 的三元组。题解 | #三数之和#_牛客网 (nowcoder.com)知识点1:哈希表
哈希表是一种根据关键码(key)直接访问值(value)的一种数据结构。而这种直接访问意味着只要知道key就能在O(1)O(1)O(1)时间内得到value,因此哈希表常用来统计频率、快速检验某个元素是否出现过等。
知识点2:双指针
双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个指针(特殊情况甚至可以多个),两个指针或是同方向访问两个链表、或是同方向访问一个链表(快慢指针)、或是相反方向扫描(对撞指针),从而达到我们需要的目的。
具体做法:
-
step 1:排除边界特殊情况。
-
step 2:既然三元组内部要求非降序排列,那我们先得把这个无序的数组搞有序了,使用sort函数优先对其排序。
-
step 3:得到有序数组后,遍历该数组,对于每个遍历到的元素假设它是三元组中最小的一个,那么另外两个一定在后面。
-
step 4:需要三个数相加为0,则另外两个数相加应该为上述第一个数的相反数,我们可以利用双指针在剩余的子数组中找有没有这样的数对。双指针指向剩余子数组的首尾,如果二者相加为目标值,那么可以记录,而且二者中间的数字相加可能还会有。
-
step 5:如果二者相加大于目标值,说明右指针太大了,那就将其左移缩小,相反如果二者相加小于目标值,说明左指针太小了,将其右移扩大,直到两指针相遇,剩余子数组找完了。
注:对于三个数字都要判断是否相邻有重复的情况,要去重。
class Solution {
public List> threeSum(int[] nums) {
List> res = new ArrayList(); // 初始化结果列表
int length = nums.length;
// 如果数组长度小于3,直接返回空列表,因为不可能有三元组
if(length 0 && nums[i] == nums[i-1]) continue;
// 初始化左右指针
int left = i + 1;
int right = length - 1;
// 计算当前目标值为当前元素的相反数
int target = -nums[i];
// 当左指针小于右指针时,执行循环
while(left target) {
// 如果两数之和大于目标值,移动右指针
right--;
} else {
// 如果两数之和小于目标值,移动左指针
left++;
}
}
}
// 返回所有找到的三元组
return res;
}
}
第二种添加方法
List list = new ArrayList(); list.add(nums[first]); list.add(nums[second]); list.add(nums[third]); ans.add(list);
HJ3 明明的随机数
-
Set set = new TreeSet(); // 直接使用TreeSet既去重又排序
-
HashSet set = new HashSet(); // 创建一个HashSet用于去除重复的整数
package hw; import java.util.Arrays; import java.util.HashSet; import java.util.Scanner; /** 描述 N个1到500之间的随机整数,删去其中重复的数字,然后再把这些数从小到大排序,按照排好的顺序输出。 输入描述: 第一行先输入随机整数的个数 N 。 接下来的 N 行每行输入一个整数,代表明明生成的随机数。 具体格式可以参考下面的"示例"。 输出描述: 输出多行,表示输入数据处理后的结果 */ public class HJ3_明明的随机数 { public static void main(String[] args) { Scanner in = new Scanner(System.in); // 使用循环来处理多个测试用例 while (in.hasNextInt()) { int n = in.nextInt(); // 首先读取整数的个数N int[] nums = new int[n]; // 创建一个数组来存储这N个整数 // 循环读取N个整数并存储到nums数组中 for(int i = 0; i set = new HashSet(); // 创建一个HashSet用于去除重复的整数 int j = 0; // j用于记录去重后的数组长度 // 遍历nums数组,去除重复的整数 for(int i = 0; iimport java.util.Scanner; import java.util.Set; import java.util.TreeSet; public class HJ3_明明的随机数 { public static void main(String[] args) { Scanner in = new Scanner(System.in); while (in.hasNextInt()) { int n = in.nextInt(); Set set = new TreeSet(); // 直接使用TreeSet既去重又排序 for(int i = 0; iHJ10 字符个数统计
import java.util.Scanner; import java.util.HashSet; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); while (in.hasNextLine()){ String str = in.nextLine(); HashSet set = new HashSet(); for(char c : str.toCharArray()){ if(c>=0 && cNC68
跳台阶牛客题霸牛客网 (nowcoder.com)
【经典算法题】【牛客网NC68】跳台阶(递归)_nc68.跳台阶 牛客网-CSDN博客
-
先更新前前状态,再更新前状态
current = prev + prev_prev; // 当前状态是前两个状态之和
prev_prev = prev; // 更新前前状态为前一个状态
prev = current; // 更新前一个状态为当前状态
package hw; /** * 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法(先后次序不同算不同的结果)。 * 数据在1到40之间;要求时间复杂度On,空间复杂度O1 */ import java.util.Scanner; public class NC68_跳台阶 { public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); int res = getStep(n); System.out.println(res); } private static int getStep(int n){ // 处理特殊情况 if(n动态规划
杨辉三角
-
第i行有i+1个元素
-
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
class Solution { public List> generate(int numRows) { List> triangle = new ArrayList(); // 如果输入的行数不大于0,则直接返回空的杨辉三角 if (numRows ()); triangle.get(0).add(1); // 从第二行开始构建杨辉三角 for (int rowNum = 1; rowNum row = new ArrayList(); List prevRow = triangle.get(rowNum - 1); // 每行的第一个元素始终是1 row.add(1); // 计算当前行中间的元素,每个元素是上一行的两个相邻元素之和 for (int j = 1; jclass Solution { public List> generate(int numRows) { List> ret = new ArrayList>(); for (int i = 0; i row = new ArrayList(); for (int j = 0; jpublic class Solution { public List> generate(int numRows) { // 初始化动态规划数组 Integer[][] dp = new Integer[numRows][]; // 遍历每一行 for (int i = 0; i > result = new ArrayList(); for (Integer[] row : dp) { result.add(Arrays.asList(row)); } // 返回结果列表 return result; } }
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net