目录(序号为leetcode题号)
14.最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 “”。
解题思路:纵向扫描法,使用第一个字符串的每一个字符与其它所有的字符串依次比较。
public String longestCommonPrefix(String[] strs) {
if(strs == null || strs.length == 0){
return "";
}
int length = 0;
int i,j;
for(i = 0; i< strs[0].length(); i++){
// 用strs[0]的每一个字符,与其它字符串的每一位比较
for(j = 1;j<strs.length;j++){
// 不相等,或其它字符串的长度不够,返回最大公共前缀
if(i >= strs[j].length() || strs[j].charAt(i) != strs[0].charAt(i)){
return strs[0].substring(0,length);
}
}
length++;
}
return strs[0];
}
15.三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
解题思路:暴力解法,找出 a b c,然后排序,去重。
然后超时了,时间复杂度为 O(n3)
public List<List<Integer>> threeSum(int[] nums) {
Set result = new HashSet();
for(int i = 0;i < nums.length;i++){
for(int j = 0;j < nums.length;j++){
if(j != i) {
for (int k = 0; k < nums.length; k++) {
if(k != j && k != i) {
if (nums[i] + nums[j] + nums[k] == 0) {
List list = new ArrayList();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
Collections.sort(list);
result.add(list);
}
}
}
}
}
}
return new ArrayList<>(result);
}
方法二:排序 + 双指针
算法思想:固定一个值,求两数之和为固定值的负值,这样两数之和与固定值的和就是0。关键在于去重。先排序,将值从小到大排列,在遍历的时候,跳过重复的值。
public List<List<Integer>> threeSum(int[] nums) {
// 排序,从小到大
Arrays.sort(nums);
int target;
int L,R,value;
List<List<Integer>> results = new ArrayList<>();
// 每次固定一个 nums[i] 值
for(int i=0; i<nums.length; i++){
// 去除重复的值。
if(i > 0 && nums[i] == nums[i - 1]){
continue;
}
// 两数之和的目标值为固定值的负值。
target = -nums[i];
L = i + 1;
R = nums.length - 1;
// 从 i值的后面的所有元素查找
while(R > L){
value = nums[R] + nums[L];
// 两数之和等于目标值
if(value == target){
ArrayList result = new ArrayList();
result.add(nums[i]);
result.add(nums[R]);
result.add(nums[L]);
// 放入结果集合
results.add(result);
// 继续寻找,排除重复的值
while(R > L && nums[R - 1] == nums[R]){
R--;
}
R--;
// 继续寻找,排除重复的值
while(R > L && nums[L + 1] == nums[L]){
L++;
}
L++;
}else if(value < target){
// 两数之和小于,则L指针右移。
L++;
}else{
// 两数之和大于,则R指针左移。
R--;
}
}
}
return results;
}
17.电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。
2: abc
3: def
4:ghi
5:jkl
6:mno
7:pqrs
8:tuv
9:wxyz
解题思路:使用深度优先,遍历得到所有的组合。
public List<String> letterCombinations(String digits) {
// 保存结果
List<String> result = new ArrayList<>();
if(digits.length() == 0){
// 为零直接返回空
return result;
}
// 建立映射表
HashMap<Character,String> map = new HashMap<Character,String>();
map.put('2', "abc");
map.put('3', "def");
map.put('4', "ghi");
map.put('5', "jkl");
map.put('6', "mno");
map.put('7', "pqrs");
map.put('8', "tuv");
map.put('9', "wxyz");
// 深度优先遍历
DFS(digits,0,map,result,new StringBuffer());
return result;
}
// 参数说明
// digits存储数字的字符串,index为数字串的下标,map为 映射表,result为结果,t保持中间的结果
public void DFS(String digits, int index,Map map,List<String> result,StringBuffer t) {
if(index == digits.length()){
// 深度优先到最深层,将中间结果t存入最终结果result中。
result.add(t.toString());
return;
}
// 得到数字对应的字母
String s = (String) map.get(digits.charAt(index));
for(int i = 0;i<s.length();i++){
// 首字母写入中间结果
t.append(s.charAt(i));
// 深度优先搜索
DFS(digits,index+1,map,result,t);
// 删除最后写入的值
t.deleteCharAt(index);
}
}
19.删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
方法一: 递归到链表尾部,然后逐层返回并计数,直到找到倒数第n个节点,返回。
static int t;
public static ListNode removeNthFromEnd(ListNode head, int n){
if(head.next == null){
t = 1;
if(n == t){
head = null;
}
return head;
}
head.next = removeNthFromEnd(head.next,n);
t++;
if(t == n){
return head.next;
}
return head;
}
方法二:快慢指针,快指针比满指针快 n 个节点。这样当快指针到达链表尾部。慢指针恰好指向第n个节点。
public static ListNode removeNthFromEnd(ListNode head, int n){
ListNode dummy = new ListNode(0, head);
ListNode first = head;
ListNode second = dummy;
for(int i = 0; i < n;i++){
first = first.next;
}
while(first != null){
first = first.next;
second = second.next;
}
second.next = second.next.next;
return dummy.next;
}
20.有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
解题思路:使用栈来解题,左括号入栈,右括号出栈。
public boolean isValid(String s) {
if(s.length()%2 == 1){
return false;
}
Stack<Character> stack = new Stack<>();
HashMap<Character,Character> map = new HashMap<>();
map.put('(',')');
map.put('[',']');
map.put('{','}');
Character c;
for(int i = 0;i < s.length();i++){
c = s.charAt(i);
if(map.containsKey(c)){
stack.push(c);
}else{
if(stack.empty() || c != map.get(stack.pop())){
return false;
}
}
}
return stack.isEmpty();
}
21.合并两个有序链表
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
解题思路:没啥说的,干。
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode result = new ListNode();
ListNode p = result;
while(list1 != null && list2 != null) {
if (list1.val <= list2.val) {
p.next = list1;
list1 = list1.next;
} else {
p.next = list2;
list2 = list2.next;
}
p = p.next;
}
p.next = list1 == null?list2:list1;
return result.next;
}
22.括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例:
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
输入:n = 1
输出:["()"]
解题思路:回溯剪枝,剪枝条件:左括号的个数大于0,右括号的个数要小于左括号的个数才能放入。
如图:n为2的情况下的所有可能,进行剪枝操作。
public List<String> generateParenthesis(int n) {
// 回溯剪枝
List<String> results = new ArrayList<>();
DFS(n,n,results,new StringBuffer());
return results;
}
public void DFS(int left,int right,List<String> results,StringBuffer t){
if(left == 0 && right == 0){
results.add(t.toString());
return;
}
if(left > 0){
t.append("(");
DFS(left-1,right,results,t);
t.deleteCharAt(t.length()-1);
}
if( right > left){
t.append(")");
DFS(left,right-1,results,t);
t.deleteCharAt(t.length()-1);
}
}
23.合并K个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
解题思路:分治算法,将大的问题转换成若干个子问题。
public ListNode mergeKLists(ListNode[] lists) {
if(lists.length == 0){
return null;
}
if(lists.length == 1){
return lists[0];
}
ListNode list;
list = divideAndConquer(lists,0,lists.length - 1);
return list;
}
public ListNode divideAndConquer(ListNode[] lists,int start, int end){
ListNode merge1 = null;
ListNode merge2 = null;
if(end - start > 1){
int mid = start + (end-start)/2;
merge1 = divideAndConquer(lists,start, mid);
merge2 = divideAndConquer(lists,mid + 1,end);
}else{
if(end == start){
merge1 = lists[start];
}else{
merge1 = mergeTwoLists(lists[start],lists[end]);
}
}
return mergeTwoLists(merge1,merge2);
}
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode result = new ListNode();
ListNode p = result;
while(list1 != null && list2 != null) {
if (list1.val <= list2.val) {
p.next = list1;
list1 = list1.next;
} else {
p.next = list2;
list2 = list2.next;
}
p = p.next;
}
p.next = list1 == null?list2:list1;
return result.next;
}
26.删除有序数组中的重复项
给你一个有序数组 nums ,请你原地删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组 并在使用 O(1) 额外空间的条件下完成。
解题思路:使用快慢指针,快指针将第一次遇见的数,赋值给慢指针指向的位置。等待快指针遍历完整个数组,就知道数组中有多少不同的值。
public int removeDuplicates(int[] nums) {
if(nums.length == 0){
return 0;
}
if(nums.length == 1){
return 1;
}
int first = 1,second = 1;
while(first < nums.length){
if(nums[first] != nums[first - 1]) {
nums[second] = nums[first];
++second;
}
first++;
}
return second;
}
28.实现 strStr()
实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
解题思路:KMP算法
next数组中的值就是子串的最大相同前缀和后缀的个数
参考视频
public int strStr(String haystack, String needle) {
int needleLength = needle.length();
// 当needle是空字符串时,返回0
if (needleLength == 0) return 0;
// 定义好next数组
int[] next = new int[needleLength];
// 定义好两个指针right与left
// 在for循环中初始化指针right为1,left=0,开始计算next数组,right始终在left指针的后面
for (int left = 0,right = 1; right < needleLength; right++) {
// 如果不相等就让left指针回退,到0时就停止回退
while (left > 0 && needle.charAt(left) != needle.charAt(right)) {
//进行回退操作;
left = next[left - 1];
}
if (needle.charAt(left) == needle.charAt(right)) {
next[right] = ++left;
}else{
next[right] = 0;
}
}
// 循环结束的时候,next数组就已经计算完毕了
// 根据next数组进行串的匹配
for (int i = 0,j=0; i <haystack.length() ; i++) {
while (j > 0 && haystack.charAt(i) != needle.charAt(j)){
j=next[j-1];
}
if (haystack.charAt(i)==needle.charAt(j)){
j++;
}
if (j == needleLength) {
return i - needleLength + 1;
}
}
return -1;
}