算法学习day02(链表/哈希表/字符串)

一、两两交换链表中的节点

问题:循环的条件应如何写。前提:在交换节点的时候,必须要借助前一个结点

节点:偶数个:当cur.next==null,说明没有节点了。

奇数个: 当cur.next.next==null,遍历结束。

对循环条件的解释:每次交换两个节点,12   34  56  78  所以借助的前一个结点一定是偶数。因此如果是偶数个,那么最后一个偶数节点的cur.next一定是null,如果是奇数个,最后一个偶数节点的cur.next.next一定是null;

代码:

while(cur.next!=null&&cur.next.next!=null){

    //开始交换,交换的时候 需要进行三次交换  需要借助临时结点存储

    ListNode temp=cur.next;(1)ListNode temp2=cur.next.next;(3)

    cur.next=cur.next.next;cur.next=temp;temp.next=temp2;

    //移动cur结点 cur=cur.next.next;

     最后 return virtual.next

}

二、删除链表中的倒数第N个结点(常规做法/双指针)

常规做法:

首先求出链表的长度length.然后算出倒数第N个结点的前一个结点。就是length-n;

然后移动到前一个结点,就可以。如何移动:

        int m=cnt-n;

        for(int i=0;i<m;i++){

            cur2=cur2.next;

        }

双指针法

核心:要删除倒数第n个结点,快指针就要比满指针多走(n+1)步,然后快慢指针同时前进,这样快指针走到末尾,慢指针就走到了要删除结点的前一个结点。一共有x个结点,慢指针要走的共有x-n-1个。

步骤1.先让快指针向前移动(n+1)个

步骤2.然后快慢指针同时移动至fast==null

步骤3.删除结点

  public ListNode removeNthFromEnd(ListNode head, int n) {

        //快慢指针法

        ListNode virtual=new ListNode();

        virtual.next=head;

        ListNode fast=virtual;

        ListNode slow=virtual;

        //将fast向后移动(n+1)个

        for(int i=1;i<=n+1;i++){

            fast=fast.next;

        }

        while(fast!=null){

            fast=fast.next;

            slow=slow.next;

        }

        //slow移动到要删除结点的前一个结点

        slow.next=slow.next.next;

        return virtual.next;

}

三、环形链表

如何判断有环:

用快慢指针判断是否有环,快指针每次都走2个,慢指针每次走1个。

如果有环的话,那么快慢指针就一定会相遇的。(慢指针进入环之后,快指针就会去追它)

如何找到入口

根据数学公式推导:(从卡尔哥那里学到的)

2(x+y)=n(y+z)+x+y;

x=(n-1)(y+z)+z;

也就是说从head到入口的距离是等于(n-1)圈循环和从相遇的点到入口点的距离。

  public ListNode detectCycle(ListNode head) {

        //首先判断有环  如果能相遇 就说明有环

        ListNode fast=head;

        ListNode slow=head;

        while(fast!=null&&fast.next!=null&&fast.next.next!=null){

            fast=fast.next.next;//快指针移动

            slow=slow.next;//慢指针移动

            if(slow==fast){

                //相等 代表相遇了

                ListNode index1=slow;

                ListNode index2=head;

                //找到入口点

                while(index1!=index2){

                    index1=index1.next;

                    index2=index2.next;

                }

                return index1;

            }

        }

        return null;

    }

四、有效的字母异位词:(哈希表数组)

使用哈希表;出现的英文字母在数组中++,然后再--,如果最后遍历数组中出现不等于0的,就说明不是有效的字母异位词。

    public boolean isAnagram(String s, String t) {

        if(s.equals(t))return false;

        int nums[] = new int[26];

        for (int i = 0; i < s.length(); i++) {

            nums[s.charAt(i)-97]++;

        }

        for (int i = 0; i < t.length(); i++) {

            nums[t.charAt(i)-97]--;

        }

        for(int i:nums){

            if(i!=0)return false;

        }

        return true;

    }

五、两个数组的交集(哈希表set)

将第一个数组arr1的每一个元素都添加到set集合中,然后遍历第二个数组arr2,看set集合中是否存在这些值,如果存在就是交集放到result(set)中。最后转化为数组,返回。

六、两数之和(map哈希表)

给定一个数组和一个target值,要求从数组中找到两个数,相加之和为target。然后返回下标

思路:

1.建立map集合用来存放遍历过的元素,key为数组值,value为数组的下标。

2.用一个for循环遍历nums中的每一个元素,判断map中是否存在target-nums[i],

3.如果存在直接返回map.get(target-nums[i]),如果不存在就往map中添加键值对

4.如果遍历结束都没找到,那么直接返回null

代码:

  public int[] twoSum(int[] nums, int target) {

        Map<Integer, Integer> map = new HashMap();// 数组值为key 下标为value

        for (int i = 0; i < nums.length; i++) {

            int result = target - nums[i];

            boolean flag = map.containsKey(result);

            if(flag){

                return new int[]{map.get(result),i};

            }else{

                map.put(nums[i],i);

            }

        }

        return null;

    }

七、四数相加:

从四个数组中每次选择一个数,要求相加之和为0,求有多少种加法。

和两数之和有一点共同之处,都用到了map集合去寻找集合中是否存在x-map.getKey();

思路:首先将数组两两相加,将相加的和和出现的次数放到map中存储,然后解题的关键就变成了:寻找0-map.getKey()这个key值在另一个map集合中是否存在以及存在多少次

找到key值后,应该是两边的value值相乘,而不是相加!!! key(数组值之和)value(出现的次数)

核心代码:

        int way = 0;
        int index = 1;
        Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
        for (Map.Entry<Integer, Integer> entry : entries) {
            int result = 0 - entry.getKey();// 在map1中找key为result的
            boolean flag = map1.containsKey(result);
            if (flag) {
                System.out.println("第" + (index++) + "次,way:" + way);
                way = way + entry.getValue() * map1.get(result);
            }

        }
        return way;

八、三数之和(双指针)

整体思路:一个for循环从0开始,i+1为left指针,nums.length-1为right指针。

在left->right这个区域中,搞成升序排列(因为题目中要求返回的是相加为0的值,不要求返回下标,因此升序更方便我们去操作)。如果nums[i]+nums[left]+nums[right]是>0的,那么right就要往左移动,right--;如果是<0的,那么left就要往右移动。left++;如果相等,那么就返回结果。

难点:如何去重?

两方面:先对a去重:当for循环遍历的时候,nums[i]和nums[i-1]相等的话,那么就直接跳过。因为这种情况我们已经在arr[i-1]的时候,考虑过了。

另一方面:对b和c去重:在收割结果之后,如果我们发现nums[left+1]或者nums[right-1]是相等的话。那么就直接跳过,因为这种条件也已经考虑过了。

代码:

 public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);//对nums进行排序
        List<List<Integer>> results=new ArrayList();
        int left;
        int right;
        for(int i=0;i<nums.length;i++){
            if(nums[i]>0)return results;
            if(i>0&&nums[i]==nums[i-1])continue;
            
            left=i+1;
            right=nums.length-1;
            while(left<right){
            if(nums[i]+nums[left]+nums[right]>0)right--;
            else if(nums[i]+nums[left]+nums[right]<0)left++;
            else{
                //该种情况是等于0 可以收割结果了
                List result=new ArrayList();
                Collections.addAll(result,nums[i],nums[left],nums[right]);
                results.add(result);
                //收割完之后 进行去重判断

                while(right>left&&nums[left]==nums[left+1])left++;
                while(right>left&&nums[right]==nums[right-1])right--;
                
                left++;right--;
            }
        }
        }
        return results;
    }

九、四数之和(和三数之和相类似 去重和剪枝好复杂(靠))

四数之和是一个双循环和双指针。在进行剪枝,两层都要考虑到。去重的时候内层去重就可以了(内外层的left和right是一样的 因此在内层去重就行了)

 public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);
       
        for (int i = 0; i < nums.length; i++) {

            //外层剪枝
            if (i > 0 && nums[i - 1] == nums[i]) {
                continue;
            }
            
            for (int j = i + 1; j < nums.length; j++) {
                //内层剪枝
                if (j > i + 1 && nums[j - 1] == nums[j]) {
                    continue;
                }

                int left = j + 1;
                int right = nums.length - 1;
                while (right > left) {
                    int sum = nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        
                        while (right > left && nums[right] == nums[right - 1]) right--;
                        while (right > left && nums[left] == nums[left + 1]) left++;

                        left++;
                        right--;
                    }
                }
            }
        }
        return result;
    }

十、反转字符串II(小难)

规则:给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

如果剩余字符少于 k 个,则将剩余字符全部反转。

如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样

反转的时候需要比较剩余的字符

1.如果剩余的字符的长度是k->2k或者>2k,那么就可以将i->i+k-1中的字符串都反转;

2.相反,如果剩余的字符串不够k,那么就将i->s.length()-1的字符串反转。

    public String reverseStr(String s, int k) {
        char[] ch=s.toCharArray();
        for(int i=0;i<ch.length;i+=2*k){
            //看范围,看反转k还是剩余的
            int start=i;
            //如果剩余的足够i+k-1 那么length-1就会>i+k-1 如果不够的话,那就是反转剩余的字符串
            int end=Math.min(i+k-1,ch.length-1);
            while(start<end){
                char temp=ch[start];
                ch[start]=ch[end];
                ch[end]=temp;
                start++;
                end--;
            }
        }
        return new String(ch);
    }

  • 42
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值