面试小知识(三)

两个链表的第一个公共节点

首先,这道题是有最简单(代码最简单)的解法的,现复制如下:

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode h1 = headA, h2 = headB;
        while (h1 != h2) {
            h1 = h1 == null ? headB : h1.next;
            h2 = h2 == null ? headA : h2.next;
        }
        return h1;  
    }
}

但是从我第一次看到这道题到现在,我都必须承认一个事实,就是这道题的这种解法并不是我能想出来的,或者说,我在十五分钟内一定想不出这么优秀的方法,现将我的两种想法阐述如下:

利用HashSet

不管在什么时候,只要涉及查找的操作,就建议第一时间考虑HashSet或者HashMap,即使复杂度不是最优,但是能AC总比挂了强

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }

        Set<ListNode> set = new HashSet<>();
        while (headA != null) {
            set.add(headA);
            headA = headA.next;
        }

        while (headB != null) {
            if (set.contains(headB)) {
                return headB;
            }

            headB = headB.next;
        }

        return null;
    }
}

这种方法没有什么思想,所以也不需要过多解释,当然,尽管理论上的时间复杂度不高,但是由于new 对象等一系列操作,效果并不是很好。

利用快慢指针

其实这个问题本质上依然是在链表中寻找一个节点,所以可以考虑使用快慢指针的做法,即假设长链表的长度为n,短链表的长度为m,如果一个指针从长链表的n - (n - m)出发,一个指针从短链表的头结点出发,则它们的第一个相同的点就是正确答案。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }

        int lenA = getLen(headA), lenB = getLen(headB);

        ListNode longNode = lenA > lenB ? headA : headB;
        ListNode shortNode = lenA <= lenB ? headA : headB;
        int diff = Math.abs(lenA - lenB);
        

        return getFirstCommonNode(longNode, shortNode, diff);
        
    }

    private int getLen(ListNode head) {
        int len = 0;
        while (head != null) {
            len++;
            head = head.next;
        }

        return len;
    }

    private ListNode getFirstCommonNode(ListNode longNode, ListNode shortNode, int diff) {
        for (int i = 0; i < diff; i++) {
            longNode = longNode.next;
        }

        while (longNode != shortNode) {
            longNode = longNode.next;
            shortNode = shortNode.next;
        }

        return shortNode;
    }
}

这样的解法可能需要写不少代码,但是思维上确实简单不少(也可能是我太笨哈哈哈哈哈哈)。

常见的存储引擎及使用场景

  • MYISAM:MySQL5.6之前的默认引擎,最常用的非事务型存储引擎
  • CSV:以CSV格式存储的非事务型存储引擎(甚至可以通过修改CSV文件来进行数据修改)
  • Archive:只允许查询和新增数据而不允许修改的非事务型存储引擎(通常用于记录日志类数据)
  • Memory:是一种易失性非事务型存储引擎(使用这种存储引擎很容易造成数据丢失,程序员很少主动使用这种存储引擎,但是在MySQL运行过程中需要存储一些中间结果的时候就可能将结果存储在使用这种存储引擎的表中)
  • InnoDB:最常用的事务型存储引擎
  • NDB:MySQL集群所使用的内存型事务存储引擎(这里的集群并不是简单的主从复制集群)

MyISAM

特点
  • 非事务型存储引擎
  • 以堆表方式存储(堆表:无序的集合存储。表和主键索引分成两个segment。创建的主键索引叶结点存储ROWID。插入和更新快,结果无序)
  • 使用表级锁:在对表进行查询操作的时候会对表加共享锁,修改表中数据的时候会对表加排它锁,读写操作之间会相互阻塞(所以MyISAM并不适合在高并发的读写混合场景中使用)
  • 支持Btree索引、空间索引、全文索引
  • 数据和索引分别进行存储,将数据存储在MYD文件中,将索引存储在MYI文件中
  • 堆表解释:堆就是无序数据的集合,索引就是将数据变得有序,在索引中键值有序,数据还是无序的。堆表中,主键索引和普通索引一样的,叶子节点存放的是指向堆表中数据的指针(可以是一个页编号加偏移量),指向物理地址,没有回表的说法。堆表中,主键和普通索引基本上没区别,和非空的唯一索引没区别。
适用场景
  • 读操作远大于写操作的场景
  • 不需要使用事务的场景

InnoDB

特点
  • 事务型存储引擎支持ACID(这里应该注意,在需要事务支持的应用场景中一定不要混合使用事务型存储引擎和非事务型存储引擎,一旦出现事务回滚,非事务型存储引擎无法进行数据回滚,从而破坏事务)
  • 数据按主键聚集存储(与堆表不同,在具有聚集索引主键的表中,每一个非主键索引的叶子节点指向的都是数据行中的主键,而不是数据行的物理存储位置,因此主键的大小会直接影响索引查找数据的性能。另一方面,由于数据是按照主键的逻辑顺序进行存储的,如果主键的顺序经常发生无规则的变化,就会造成数据的经常迁移,造成一定的IO问题;通常情况下,如果要使用InnoDB引擎的表,就应该使用一个自增ID列作为主键)
  • 支持行级锁及MVCC(行级锁:在读写的过程中,只会在需要的行上加锁,而不会像MyISAM一样,对整个表进行加锁,增强了数据处理的并发能力;MVCC:多版本并发控制,避免读写操作的相互阻塞)
  • 支持Btree索引和自适应Hash索引(自适应Hash索引:由InnoDB存储引擎根据数据的信息在内存中建立的Hash索引,这种索引只能供等值查找使用,这个过程不需要程序员进行干预)
  • 支持全文索引和空间索引

MVCC

这里已经解释的非常好了
https://www.jianshu.com/p/8845ddca3b23

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值