算法-数组与链表(Java实现)

链表

1.链表的逆置

当打算修改输入的数据时,最好问清面试官是否可以!!!

思路:

  1. 使用栈的结构,时间复杂度O(n),空间复杂度O(n)
  2. 递归,时间复杂度O(n),空间复杂度O(n),由于使用递归,将会使用隐式栈空间。递归深度可能会达到 n 层。
  3. 修改输入数据,逆置链表,时间复杂度O(n),空间复杂度O(1)
  • 使用栈的结构
   //栈
 public ListNode reverseList(ListNode head) {
        if (head == null) {
            return null;
        }
        Stack<ListNode> st = new Stack<ListNode>();
        ListNode curr = head;
        while (curr != null) {
            st.push(curr);
            curr = curr.next;
        }
        ListNode prev = st.pop();
        ListNode result = prev;
        while (!st.isEmpty()) {
            ListNode ptr = st.pop();
            //拿出后先和置空,避免产生循环
            ptr.next = null;
            //改变指向即可
            prev.next = ptr;
            prev = ptr;
        }
        return result;
    }
  • 递归的方式
      //递归
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null) {
            return head;
        }
        ListNode ptr = reverseList(head.next);
        // 要使用 head 的 next.next 去逆置
        head.next.next = head;
        head.next = null;
        return ptr;
    }
  • 迭代的方式(头插法嘛)
/**
 * 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) {
        if (head == null) {
            return null;
        }
        ListNode prev, curr, nextPtr;
        prev = null;
        curr = head;
        while (curr != null) {
            //先保存下头插的下一个节点,不然就找不到链表了
            nextPtr = curr.next;
            //头插
            curr.next = prev;
            prev = curr;
            //将需要头插的节点移动
            curr = nextPtr;
        }
        return prev;
    }
}

2.在O(1)时间内删除链表节点

请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
输入: head = [4,5,1,9], node = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

思路:OK。我们现在知道了需要删除的节点。那么我们把后面节点的内容覆盖到前面需要删除的节点,然后把后面的节点删除掉就行了呗。

时间复杂度O(1)

注意有几大坑点:

  • 只有一个节点,删除节点位于头节点(也是尾节点)
  • 删除节点位于尾节点
  • 需要调用者确保要删除的节点在该链表中。
void DeleteNode(ListNode *head, ListNode *deListNode)
{
    if (deListNode == nullptr || head == nullptr)
        return;
    //1.普通情况
    if (deListNode->next)
    {
        ListNode *p = deListNode->next;

        deListNode->val = deListNode->next->val;
        deListNode->next = deListNode->next->next;
        delete p;
        p = nullptr;
    }
    //2.只有一个节点
    else if (head == deListNode)
    {
        delete deListNode;
        deListNode = nullptr;
        head = nullptr;
    }
    //3.删除的节点是尾节点
    else
    {
        ListNode *p = head;
        while (p->next != deListNode)
        {
            p = p->next;
        }
        p->next = nullptr;
        delete deListNode;
        deListNode = nullptr;
    }
}

3.链表中的倒数第k个节点

输入一个链表,输出该链表中倒数第k个结点。

思路:双指针思路。时间复杂度 O(n)

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution
{
public:
    ListNode *FindKthToTail(ListNode *head, unsigned int k)
    {
        if (head == nullptr || k == 0)
            return nullptr;

        ListNode *p1 = head;
        ListNode *p2 = head;
    
        for (int i = 0; i < k-1 ; i++) //注意 k-1 
        {
            if (p2->next)
                p2 = p2->next;
            else //如果 k  比链表整个的长度还要长,怎么办?
                return nullptr;
        }
        while (p2->next)//不需要循环到 nullptr 
        {
            p1 = p1->next;
            p2 = p2->next;
        }
        return p1;
    }
};

拓展:找最中间的节点和判断是否成环也是双指针的思路

4.合并两个排序的链表

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

6.环形链表(圆圈中最后剩下的数字)

判断是否有环(快慢指针&&龟兔赛跑思想)
public class Solution {
    public boolean hasCycle(ListNode head) {
          if (head == null) {
            return false;
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while(fast != null  && fast.next != null){
            fast =  fast.next.next;
            slow = slow.next;
            if(fast == slow){
                return true;
            }
        }
        return false;
    }
}
  • 约瑟夫环的问题。时间复杂度O(MN),空间复杂度O(N)
class Solution
{
public:
    int LastRemaining_Solution(int n, int m)
    {
        if (n < 1 || m < 1)
            return -1;

        std::list<int> ll;
        for (int i = 0; i < n; i++)
            ll.push_back(i);

        auto it = ll.begin();
        while (ll.size() != 1)
        {
            for (int i = 0; i < m - 1; i++)
            {
                it++;
                if (it == ll.end())
                    it = ll.begin();
            }
            //删除 第  m  个
            auto tmp = it;
            it++;
            if (it == ll.end())
                it = ll.begin();
            ll.erase(tmp);
        }
        return *it;
    }
};
  • 经过上面复杂的分析,我们终于找到了一个递归公式。要得到n个数字的序列中最后剩下的数字,只需要得到n-1个数字的序列中最后剩下的数字,并以此类推。当n=1时,也就是序列中开始只有-一个数字0,那么很显然最后剩下的数字就是0。我们把这种关系表示为:
    在这里插入图片描述
    时间复杂度O(n),空间复杂度O(1)
class Solution
{
public:
    int LastRemaining_Solution(int n, int m)
    {
        if (n < 1 || m < 1)
            return -1;
            
        int last = 0;
        for (int i = 2; i <= n; i++)
            last = (last + m) % i;
        return last;
    }
};

7.二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路:

在这里插入图片描述
时间复杂度O (n)

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/
class Solution {
public:
    //左 根 右
    TreeNode *lastMax = nullptr ;
    void ConvertNode(TreeNode* node){
       if(node)
        {
            if(node->left)
                ConvertNode(node->left);
            
            node->left = lastMax;
            if(lastMax)
                lastMax->right = node;
            lastMax = node ;
            
            if(node->right)
                ConvertNode(node->right);
        }
    }
    TreeNode* Convert(TreeNode* root)
    {
        if(root == nullptr )
           return nullptr;
         //先找到需要返回的节点的指针
        TreeNode *ret = root;
        while(ret->left)
            ret = ret->left;
         
        ConvertNode(root);
         
        return ret;
    }
};

8.复杂链表的复制

给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。

要求返回这个链表的深拷贝。


class Solution
{
public:
    //1.复制节点
    void CloneNodes(Node *pHead)
    {
        Node *p = pHead;
        while (p)
        {
            Node *pNew = new Node(p->val, nullptr, nullptr);//一定要置空,不然过不了
            pNew->next = p->next; //节点的中间插入法
            p->next = pNew;
            p = pNew->next;
        }
    }
    //2.设置 random 指针
    void SetRandom(Node *pHead)
    {
        Node *p = pHead;
        Node *pClone = nullptr;
        while (p)
        {
            pClone = p->next;
            if (p->random)
            {
                pClone->random = p->random->next;
            }
            p = pClone->next;
        }
    }
    //3.将复制链表从原链表分离。
    Node *GetResult(Node *head)
    {
        Node *node = head;
        Node *newHead = head->next;
        Node *newNode = head->next;
        while (node != nullptr)
        {
            node->next = node->next->next;
            if (newNode->next != nullptr)
            {
                newNode->next = newNode->next->next;
            }
            node = node->next;
            newNode = newNode->next;
        }
        return newHead;
    }

    Node *copyRandomList(Node *pHead)
    {
        if (pHead == nullptr)
            return nullptr;
        CloneNodes(pHead);
        SetRandom(pHead);
        return GetResult(pHead);
    }
};

9.两两交换链表中的节点

  • 递归法:

  • 迭代法:

class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head == null) {
            return head;
        }
        ListNode dumpHead = new ListNode();
        ListNode dumpTmp;
        ListNode left, right;
        dumpHead.next = head;
        dumpTmp = dumpHead;
        //对于头结点改变的都创建一个 哑结点 dummyHead
        while (dumpTmp.next != null && dumpTmp.next.next != null) {
            left = dumpTmp.next;
            right = dumpTmp.next.next;
            // 交换 left 和 right
            left.next = right.next;
            dumpTmp.next = right;
            right.next = left;
            //移动 dumpTmp
            dumpTmp = left;
        }
        return dumpHead.next;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值