287.给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
示例 1:
输入:nums = [1,3,4,2,2]
输出:2
示例 2:输入:nums = [3,1,3,4,2]
输出:3
提示:
1 <= n <= 105
nums.length == n + 1
1 <= nums[i] <= n
nums 中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次
进阶:
如何证明 nums 中至少存在一个重复的数字?
你可以设计一个线性级时间复杂度 O(n) 的解决方案吗?
1.二分法,抽屉思想,如果小于mid的数的数目超过mid的值,说明重复数一定在比mid大。注意标杆是下标不是数值。
class Solution {
public int findDuplicate(int[] nums) {
//二分法
int length=nums.length;
int left=0;
int right=length;
int mid=0;
int count=0;
while(left<right){
mid=(left+right)/2;
count=0;
//统计小于mid的数的数目
for(int i=0;i<length;i++){
if(nums[i]<=mid){
count++;
}
}
if(count>mid){
right=mid;
}else{
left=mid+1;
}
}
return left;
}
}
2.二进制法

1<<i,将1左移i位。
class Solution {
public int findDuplicate(int[] nums) {
//二进制法
//统计1-n各列的1的个数,然后统计现有的,看哪几位不一样。
int length=nums.length;
int res=0;
for(int i=0;i<32;i++){//int类型的二进制都在32位以内
int k=(1<<i);//1左移i位,一个一个来
int sum1=0;
int sum2=0;
for(int j=0;j<length;j++){
if((nums[j]&k)!=0){
sum1++;
}
}
for(int m=1;m<length;m++){
if((m&k)!=0){
sum2++;
}
}
//现在sum1的结果是原数组第i位为1的数的个数,sum2为1-n中第i位为1的数的个数
if(sum1>sum2){
res=res|k;
}
}
return res;
}
}
3.把数组看成链表,用快慢指针、、没太懂
class Solution {
public int findDuplicate(int[] nums) {
//把这个数组看成一个链表,她的值为next,
//则这个链表一定有环,然后用快慢指针找环与直线的交点即可
int slow = 0, fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);//快慢相遇了,把慢的放到起点,然后继续走,再次相遇在交点。
slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}
49.给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:输入: strs = [""]
输出: [[""]]
示例 3:输入: strs = ["a"]
输出: [["a"]]
提示:
1 <= strs.length <= 104
0 <= strs[i].length <= 100
strs[i] 仅包含小写字母
getOrDefault方法:Map集合中有这个key时,就使用这个key对应的value值,如果没有就使用默认值defaultValue。
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<String, List<String>>();//只能用string做key
for (String str : strs) {
char[] array = str.toCharArray();
Arrays.sort(array);
String key = new String(array);
List<String> list = map.getOrDefault(key, new ArrayList<String>());//如果有就取出来当list,没有就new一个
list.add(str);//在取出来的基础上加上当前这个,然后存进去
map.put(key, list);
}
return new ArrayList<List<String>>(map.values());
}
}
计数法:和上面那个基本相同, 但空间复杂度和时间复杂度应该都略高。
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<String, List<String>>();
for (String str : strs) {
int[] counts = new int[26];
int length = str.length();
for (int i = 0; i < length; i++) {
counts[str.charAt(i) - 'a']++;
}
// 将每个出现次数大于 0 的字母和出现次数按顺序拼接成字符串,作为哈希表的键
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 26; i++) {
if (counts[i] != 0) {
sb.append((char) ('a' + i));
sb.append(counts[i]);
}
}
String key = sb.toString();
List<String> list = map.getOrDefault(key, new ArrayList<String>());
list.add(str);
map.put(key, list);
}
return new ArrayList<List<String>>(map.values());
}
}
5.给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:输入:s = "cbbd"
输出:"bb"
提示:
1 <= s.length <= 1000
s 仅由数字和英文字母组成
1.暴力解法 -经典超时

class Solution {
public String longestPalindrome(String s) {
//遍历所有字串
String result=new String();
int maxSize=0;
int l=s.length();
for(int i=0;i<l;i++){
for(int j=i+1;j<=l;j++){
//判断当前字串是不是回文串,如果是,且长度大于最大长度,用一个临时变量保存它
String curr=s.substring(i,j);
if(isPalindrome(curr)&&(j-i)>maxSize){
result=curr;
maxSize=j-i;
}
}
}
return result;
}
//判断一个字符串是不是回文字符串
public boolean isPalindrome(String s){
int length=s.length();
for(int i=0;i<length/2;i++){
if(s.charAt(i)!=s.charAt(length-1-i)){
return false;
}
}
return true;
}
}
2.反转求最大公共子串
---------------------------------------------------------------------------------------------------------------------------------
53.给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:输入:nums = [1]
输出:1
示例 3:输入:nums = [5,4,-1,7,8]
输出:23
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。
class Solution {
public int maxSubArray(int[] nums) {
//原地修改数组
for(int i=1;i<nums.length;i++){
nums[i]=Math.max(nums[i]+nums[i-1],nums[i]);
}
int max=nums[0];
for(int i=1;i<nums.length;i++){
if(nums[i]>max){
max=nums[i];
}
}
return max;
}
}
优化一下,只遍历一次
class Solution {
public int maxSubArray(int[] nums) {
//原地修改数组
int max=nums[0];
for(int i=1;i<nums.length;i++){
nums[i]=Math.max(nums[i]+nums[i-1],nums[i]);
max=Math.max(max,nums[i]);
}
return max;
}
}
分治,打死我也不写。
221.在一个由 '0' 和 '1' 组成的二维矩阵内,找到只包含 '1' 的最大正方形,并返回其面积。
示例 1:
输入:matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]]
输出:4
示例 2:
输入:matrix = [["0","1"],["1","0"]]
输出:1
示例 3:输入:matrix = [["0"]]
输出:0
提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 300
matrix[i][j] 为 '0' 或 '1'
动态规划实牛!
class Solution {
public int maximalSquare(char[][] matrix) {
int maxSide = 0;
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return maxSide;
}
int rows = matrix.length, columns = matrix[0].length;
int[][] dp = new int[rows][columns];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
if (matrix[i][j] == '1') {
//构建两个边边,如果不是字符矩阵是数字矩阵,就方便很多。
if (i == 0 || j == 0) {
dp[i][j] = 1;
} else {
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
}
maxSide = Math.max(maxSide, dp[i][j]);
}
}
}
return maxSide*maxSide;
}
}
3.给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示:
0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成
滑动窗口法
class Solution {
public int lengthOfLongestSubstring(String s) {
int n=s.length();
if(n==0||n==1){
return n;
}
//用哈希集合?添加失败说明重复了
Set<Character> hashSet=new HashSet<>();
int temp=0;
int j=0;
for(int i=0;i<n;i++){
if(i!=0){
hashSet.remove(s.charAt(i-1));
}
while(j<n&&!hashSet.contains(s.charAt(j))){
hashSet.add(s.charAt(j));
j++;
}
temp=Math.max(temp,j-i);
}
return temp;
}
}
4.给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m=nums1.length;
int n=nums2.length;
//二分查找
int k=m+n;
if(k==0){
return 0.0;
}
if((k&1)==1){
return findIt(nums1,nums2,k/2+1);
}else{
return (findIt(nums1,nums2,k/2)+findIt(nums1,nums2,k/2+1))/2.0;
}
}
public double findIt(int[] nums1,int[] nums2,int it){
//寻找第it个元素
int l1=nums1.length;
int l2=nums2.length;
int index1=0;
int index2=0;
while(true){
//1.边界情况
//为什么不是0的判断?因为中途可能会有一个数组已经满了,不仅仅是一开始
if(index1==l1){
return nums2[index2+it-1];
}
if(index2==l2){
return nums1[index1+it-1];
}
if(it==1){
return Math.min(nums1[index1],nums2[index2]);
}
//2.正常情况
int temp=it/2;
int n1=Math.min(index1+temp,l1)-1;
int n2=Math.min(index2+temp,l2)-1;
int pivot1=nums1[n1];
int pivot2=nums2[n2];
if(pivot1<=pivot2){
it-=(n1-index1+1);
index1=n1+1;
}else{
it-=(n2-index2+1);
index2=n2+1;
}
}
}
31.整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。必须 原地 修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:输入:nums = [1,1,5]
输出:[1,5,1]
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 100
class Solution {
public void nextPermutation(int[] nums) {
int i = nums.length - 2;
//找不逆序的第一个数
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
//找比这个数大的数并交换
if (i >= 0) {
int j = nums.length - 1;
while (j >= 0 && nums[i] >= nums[j]) {
j--;
}
swap(nums, i, j);
}
//反转i后面的序列
for(int m=i+1,n=nums.length-1;m<n;m++,n--){
swap(nums,m,n);
}
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
10.给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
示例 1:输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。
示例 2:输入:s = "aa", p = "a*"
输出:true
解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:输入:s = "ab", p = ".*"
输出:true
解释:".*" 表示可匹配零个或多个('*')任意字符('.')。
提示:
1 <= s.length <= 20
1 <= p.length <= 30
s 只包含从 a-z 的小写字母。
p 只包含从 a-z 的小写字母,以及字符 . 和 *。
保证每次出现字符 * 时,前面都匹配到有效的字符
动态规划-直接用字符串匹配会出现经典超时!!!
class Solution {
public boolean isMatch(String s, String p) {
//浅写一个动态规划,f[i][j]代表这s的前i个字符和p的前j个字符是否匹配
//如果p[j]是普通字符,当s[i]与p[j]匹配,f[i][j]=f[i-1][j-1],否则返回false
//如果p[j]是.,f[i][j]=f[i-1][j-1]
//如果p[j]是*,考虑几种情况,如果p[j-1]与s[i]匹配,那就f[i][j]=f[i-1][j],
//如果不匹配,那就用*消去这个不匹配的,相当于f[i][j]=f[i][j-2]
int m=s.length();
int n=p.length();
boolean[][] res=new boolean[m+1][n+1];
res[0][0]=true;
//单纯的i或j为0都是false。
for(int i=0;i<=m;i++){
for(int j=1;j<=n;j++){
if(p.charAt(j-1)=='*'){
if(matches(s,p,i,j-1)){
res[i][j]=res[i][j-2]||res[i-1][j];//attention!!!!
}else{
res[i][j]=res[i][j-2];
}
}else{
if(matches(s,p,i,j)){
res[i][j]=res[i-1][j-1];
}
}
}
}
return res[m][n];
}
public boolean matches(String s, String p, int i, int j) {
if (i == 0) {
return false;
}
if (p.charAt(j - 1) == '.') {
return true;
}
return s.charAt(i - 1) == p.charAt(j - 1);
}
}
15、给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:输入:nums = []
输出:[]
示例 3:输入:nums = [0]
输出:[]
提示:
0 <= nums.length <= 3000
-105 <= nums[i] <= 105
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int n=nums.length;
Arrays.sort(nums);
List<List<Integer>> res=new ArrayList<>();
List<Integer> l=new ArrayList<>();
for(int i=0;i<n;i++){
//如果第一个数字重复,就走到下一个第一个数字
if(i>0&&nums[i]==nums[i-1]){
continue;
}
int k=n-1;
int target=-nums[i];
for(int j=i+1;j<n;j++){
//如果第二个数字重复,就走到下一个第二个数字
if(j>i+1&&nums[j]==nums[j-1]){
continue;
}
//找到j和k满足和为target。
//要求j在k左边
while(j<k&&nums[j]+nums[k]>target){
k--;
}
if(j==k){
break;
}
if(nums[j]+nums[k]==target){
l=new ArrayList<>();
l.add(nums[i]);
l.add(nums[j]);
l.add(nums[k]);
res.add(l);
}
}
}
return res;
}
}
本文探讨了多种算法技巧解决编程问题,如二分查找、二进制法、滑动窗口和动态规划,包括查找数组中唯一重复数字、字母异位词组合、最长回文子串、子数组最大和、无重复字符子串长度、中位数和整数数组排列等。通过实例解析和代码实现,展示了如何在常量空间内优化问题求解。
276

被折叠的 条评论
为什么被折叠?



