★ 字符串相关 ★
3.无重复字符的最长子串
思路:不重复,先考虑HashSet。
双指针法,左指针用于从左到右一个一个的变换子串的开头,右指针在子串头确定以后一直往后遍历。右指针遍历到的如果是第一次出现就加入set;每次右指针循环退出(包括两种情况,要不是遍历完了,要不就是右重复),这时候左指针右移,右移后要把前面这个从set中remove,因为左指针管的是每次子串的头部,现在头部换了。
class Solution {
public int lengthOfLongestSubstring(String s) {
HashSet<Character> set = new HashSet<>();
int len = s.length();
int right = 0, nums = 0; //右指针从0开始遍历
//这里的循环是遍历左指针
for(int i = 0;i < len; i++){
if(i!= 0){
set.remove(s.charAt(i-1)); //为什么是i-1,因为一次循环完之后,i++了,要移除的是i前面的元素
}
while(right < len && !set.contains(s.charAt(right))){//只要集合中不包含右指针的元素,就不断的往集合添加
set.add(s.charAt(right));
right++;
}
nums = Math.max(nums,right - i);
}
return nums;
}
}
5.最长回文子串
回文子串一个字符串的子串正着读反着读都一样,比如aba,就是镜像对称的。因此我们有这样的思路:从中心向外扩展,设定左右指针,一种可能是左边或右边的元素和目前指定的元素相同,如若不然就是该元素的左右元素相同。
class Solution {
public String longestPalindrome(String s) {
if(s == null ||s.length()<1){
return "";
}
int strLen = s.length(),left = 0, right = 0, len = 1,start = 0, maxLen = 0;
for(int i = 0; i < strLen; i++){
left = i-1;
right = i+1;
while(left>=0&&s.charAt(left) == s.charAt(i)){
left--;
len++;
}
while(right<strLen&&s.charAt(right) == s.charAt(i)){
right++;
len++;
}
while(left>=0&&right<strLen&&s.charAt(right)==s.charAt(left)){
right++;
left--;
len = len+2;
}
if(len > maxLen){
maxLen = len;
start = left;
}
len = 1;
}
return s.substring(start+1,start + maxLen +1);
}
}
28.实现strStr()
要求在串1中找到串2出现在其中的第一个位置
class Solution {
public int strStr(String haystack, String needle) {
int n = haystack.length();
int m = needle.length();
for(int i = 0; i + m <= n; i++){
boolean flag = true;
for(int j = 0; j < m; j++){
if(haystack.charAt(i+j)!=needle.charAt(j)){
flag = false;
break;
}
}
if(flag){
return i;
}
}
return -1;
}
}
415.字符串相加
要求元素是整数字符串的两个串相加。
思路,把每一个字符取出来,按照小学的那种竖着来加的方法一个元素一个元素的加,同时要考虑两个问题,一个是进位,一个是字符取出来之后怎么表达为10进制的问题,后一个问题的解决办法是让这个字符减去’0’字符,因为字符按照编码方法字符’2’与字符’0’相减就是十进制2,因为它们编码相差2。
class Solution {
public String addStrings(String num1, String num2) {
int i = num1.length()-1,j = num2.length()-1, add = 0;
StringBuffer res = new StringBuffer();//用来往里添加元素结果,最开始确定不到大小
while(i>=0||j>=0||add == 1){
int x = i >=0 ? num1.charAt(i) - '0' : 0; //-'0'的作用是得到第i个字符的十进制值
int y = j >=0 ? num2.charAt(j) - '0' : 0;
int addRes = x + y +add;
res.append(addRes % 10);
add = addRes/10; //除就是要截断,不是0就是进位1
i--;
j--;
}
return res.reverse().toString();
}
}
1047.删除字符串中所有相邻重复项
“abbaca”输出“ca”,我感觉又可以用栈做,又带了一些匹配的影子。也蒜是栈的思想吧。遍历字符串元素,添加到StringBuffer中,每添加一个,让top指针指向buffer的当前元素,如果要添加的下一个元素和这个top指向的元素相同就把它取出来。
class Solution {
public String removeDuplicates(String s) {
StringBuffer stack = new StringBuffer();
int top = -1;
for(int i = 0;i < s.length(); i++){
char c = s.charAt(i);
if(top>=0&&stack.charAt(top)==c){
stack.deleteCharAt(top);
top--;
}
else{
stack.append(c);
top++;
}
}
return stack.toString();
}
}
★ 数组相关 ★
剑指offer03
数组中的重复数字思路:遍历数组元素,首次出现的就扔到set里,后续遍历如果set里有这个数,那么就返回这个数的下标,说明找到了重复的数字。
public class Solution {
public int findRepeatNumber(int[] nums) {
HashSet<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length ; i++) {
if(set.contains(nums[i])){
return nums[i];
}
else{
set.add(nums[i]);
}
}
return 0;
}
}
1.两数之和
这个题目要求在整数数组中,找到和为target的两个数返回他们的下标。
思路:最暴力的,两次遍历,第二层遍历j从i+1开始,因为前面的已经遍历过了。那么让num[i]+num[j]=target就算找到了。这种比较简单。
public class Solution {
public int[] twoSum(int[] nums,int target) {
int n = nums.length;
for (int i = 0; i < n ; i++) {
for (int j = i+1; j < n; j++) {//两次遍历
if(target == nums[i]+nums[j]){
return new int[]{i,j};
}
}
}
return new int[0];
}
}
15.三数之和
判断整数数组中是否存在a+b+c=0
思路:想都不用想,上来先双指针法,找三个数比较困难,因此先给数组排序后,在有序的情况下也方便跳出临界条件。
class Solution{
public List<List<Integer>> threeSum(int[] sums){
int n = sums.length;
Arrays.sort(sums);
List<List<Integer>> ans = new ArrayList<List<Integer>>();
for (int first = 0; first < n; first++) {
if(first>0 && sums[first] == sums[first-1]){//枚举的前后两个数相同,跳出本次循环
continue;
}
int third = n-1;//第三个数从最后一个开始枚举
int target = -sums[first] ;
int second = first + 1 ;//目标值让另外两个加起来等于它,因为三个加起来是0
while(third > second){
if(sums[second] + sums[third] == target){
ArrayList<Integer> list = new ArrayList<>();
list.add(sums[first]);
list.add(sums[second]);
list.add(sums[third]);
ans.add(list);
second++;
third--;
while(second<third&&sums[second]==sums[second-1]){
second++;
}
while(second<third&&sums[third]==sums[third+1]){
third--;
}
}else if(sums[second] + sums[third] <target){
second++;
}else{
third--;
}
}
}
return ans;
}
}
★ 链表相关 ★
反转链表
思路:很简单,让每一个节点都指向原本它前一个节点即可。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while(cur!=null){
ListNode next1 = cur.next; //应该是把cur。next保存下来
cur.next = pre;
pre = cur;
cur = next1;
}
return pre;
}
}
21.合并两个有序链表
思路:先创建一个虚拟的头结点,链表的感觉就是在于虚拟结点的灵活运用。
如果L1的data比L2的data小,就让虚拟节点连L1,否则就连L2,然后指向L1和L2的指针往后移动,一直比,当有一个为空了就不用比了,哪个不是空,就挂在前面新组成的链表后面,然后返回的是虚拟头节点的下一个节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode pre = new ListNode(-1);
ListNode prehead = pre;
while(l1!=null&&l2!=null){
if(l1.val<=l2.val){
pre.next = l1;
l1 = l1.next;
}
else{
pre.next = l2;
l2 = l2.next;
}
pre = pre.next;
}
pre.next = l1==null?l2:l1;
return prehead.next;
}
}
★ 栈、队列相关 ★
20.有效的括号
要求左右括号以相同类型以及正确的顺序闭合,例如[]{}()或者{([])}。
思路:匹配问题利用进栈和出栈来解决。遇到左括号就压入栈,当遇到右括号,根据栈的后进先出的原则,当现在是一个}的右括号,那么栈顶弹出来的一定是一个{才对,直到最后栈空说明都匹配好了。
class Solution {
public boolean isValid(String s) {//对于栈的操作,就是出栈入栈,解决匹配问题,表达式的值,利用好出栈和入栈的顺序
int n = s.length();
if(n % 2 == 1){
return false;
}
Stack<Character> stack = new Stack<>();
for(int i = 0 ; i < n; i++){
char c = s.charAt(i);
if(c=='('||c=='['||c=='{'){
stack.push(c);//左括号压入栈
}
else{
if(stack.isEmpty()){
return false;
}
char top = stack.pop();
if(c ==')' && top !='('){
return false;
}
else if(c ==']' && top !='['){
return false;
}
else if(c =='}' && top !='{'){
return false;
}
}
}
return stack.isEmpty();
}
}
剑指offer9.用两个栈实现队列
思路:栈是先入后出,队列是先入先出。入队的时候,只要栈1工作即可,那么出队,由于最先入队的要最先出来,那么需要让栈底元素转到栈顶,才能获得先出的效果,那么就让栈一的元素全部放入栈2,由栈2实现出队。
class CQueue {
LinkedList<Integer> stack1;
LinkedList<Integer> stack2;
public CQueue() {
stack1 = new LinkedList();
stack2 = new LinkedList();
}
public void appendTail(int value) {
stack1.push(value);
}
public int deleteHead() {
if(stack2.isEmpty()){
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
if(stack2.isEmpty()){ //已经把栈1的所有数扔进栈2,如果栈1一开始就是空的,呢么也不会向栈2扔数据,直接返回-1
return -1;
}
else{
int delidx = stack2.pop(); //栈2只要不空,那它一定比新加进来的栈1的数据进来的早,要删也是从栈2删,直到删完
return delidx;
}
}
}